昨日、新しいC#の「非同期」機能について、特に生成されたコードがどのように見えるか、およびthe GetAwaiter()/ BeginAwait()/ EndAwait()呼び出しについて詳しく説明しました。
C#コンパイラーによって生成された状態マシンを詳細に調べたところ、理解できない2つの側面がありました。
- 生成されたクラスにDispose()メソッドと$__disposing変数が含まれているのに、それらが使用されていないように見える(クラスがを実装していないIDisposable)理由。
- 通常、0が「これが初期エントリポイントであること」を意味するように見えるときに、内部state変数が0に設定される理由EndAwait()
最初の点は、非同期メソッド内でもっと興味深いことを実行することで解決できると思いますが、誰か他の情報があれば、聞いて喜んでいます。ただし、この質問は2番目の点についての詳細です。
非常にシンプルなサンプルコードを次に示します。
using System.Threading.Tasks;
class Test
{
    static async Task<int> Sum(Task<int> t1, Task<int> t2)
    {
        return await t1 + await t2;
    }
}...そしてこれがMoveNext()、ステートマシンを実装するメソッドのために生成されるコードです。これはReflectorから直接コピーされます-私は言葉にできない変数名を修正していません:
public void MoveNext()
{
    try
    {
        this.$__doFinallyBodies = true;
        switch (this.<>1__state)
        {
            case 1:
                break;
            case 2:
                goto Label_00DA;
            case -1:
                return;
            default:
                this.<a1>t__$await2 = this.t1.GetAwaiter<int>();
                this.<>1__state = 1;
                this.$__doFinallyBodies = false;
                if (this.<a1>t__$await2.BeginAwait(this.MoveNextDelegate))
                {
                    return;
                }
                this.$__doFinallyBodies = true;
                break;
        }
        this.<>1__state = 0;
        this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();
        this.<a2>t__$await4 = this.t2.GetAwaiter<int>();
        this.<>1__state = 2;
        this.$__doFinallyBodies = false;
        if (this.<a2>t__$await4.BeginAwait(this.MoveNextDelegate))
        {
            return;
        }
        this.$__doFinallyBodies = true;
    Label_00DA:
        this.<>1__state = 0;
        this.<2>t__$await3 = this.<a2>t__$await4.EndAwait();
        this.<>1__state = -1;
        this.$builder.SetResult(this.<1>t__$await1 + this.<2>t__$await3);
    }
    catch (Exception exception)
    {
        this.<>1__state = -1;
        this.$builder.SetException(exception);
    }
}長いですが、この質問の重要な行は次のとおりです。
// End of awaiting t1
this.<>1__state = 0;
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();
// End of awaiting t2
this.<>1__state = 0;
this.<2>t__$await3 = this.<a2>t__$await4.EndAwait();どちらの場合も、次に明らかに観察される前に、後で状態が再び変更されます...では、なぜそれをまったく0に設定するのでしょうか。MoveNext()この時点で(直接またはを介してDispose)再度呼び出された場合、効果的にasyncメソッドが再び開始されます。これは、私が知る限り、完全に不適切です... 呼び出されMoveNext() ない場合、状態の変化は関係ありません。
これは、非同期の反復子ブロック生成コードを再利用するコンパイラの副作用であり、より明確な説明があるかもしれませんか?
重要な免責事項
明らかに、これは単なるCTPコンパイラです。私は、最終リリースの前に、そしておそらく次のCTPリリースの前にさえ、物事が変化することを完全に期待しています。この質問は、これがC#コンパイラまたはそのようなものの欠陥であると主張しようとするものでは決してありません。私が見逃したこのための微妙な理由があるかどうかを考え出そうとしているだけです:)