[解決済み】C# 5 非同期 CTP:生成されたコードで EndAwait 呼び出しの前に内部の "state" が 0 に設定されるのはなぜですか?
質問
昨日、C#の新機能である "async"について、特に生成されたコードがどのように見えるかを掘り下げて講演していたのですが、その際に
the GetAwaiter()
/
BeginAwait()
/
EndAwait()
を呼び出します。
C#コンパイラが生成するステートマシンを詳しく見てみたのですが、理解できない点が2つありました。
-
なぜ生成されたクラスには
Dispose()
メソッドと$__disposing
という変数がありますが、これらは一度も使われていないようです (そして、このクラスはIDisposable
). -
なぜ、内部の
state
を呼び出す前に0に設定されます。EndAwait()
通常、0は「最初のエントリーポイントです」という意味になります。
最初の点は、asyncメソッドの中でもっと面白いことをすれば答えが出るのではないかと思いますが、もし誰かがもっと詳しい情報を持っていれば、それを教えていただけるとうれしいです。しかし、この質問は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
を実行すると、非同期メソッドを再び開始することになり、私の知る限りではまったく不適切です。
MoveNext()
ではありません。
が呼び出された場合、状態の変化は関係ありません。
これは単にコンパイラがイテレータブロック生成コードを非同期用に再利用したことによる副作用で、もっと明白な説明があるのではないでしょうか?
重要な免責事項
もちろん、これは単なるCTPコンパイラです。最終的なリリースまでに、あるいは次のCTPのリリースまでに、状況が変化することは十分に予想されます。この質問は、これがC#コンパイラの欠陥であるとか、そのようなことを主張するものでは決してありません。ただ、私が見落としているような微妙な理由があるのかどうかを調べたいだけです :)
解決方法は?
さて、ようやく本当の答えが出ました。VBチームのLucian Wischikが、本当に理由があることを確認した後、私は自分自身でそれを解決したのです。彼に感謝します。 ブログ (上の アーカイブドットオルグ )は、ロックです。
ここで値0が特別なのは、それが
ではない
の直前にいる可能性のある有効な状態です。
await
を使用することで、通常の場合 特に、ステートマシンが他の場所でテストすることになるような状態ではないのです。正でない値を使っても同じように動作すると思います。
論理的に
は通常、終了を意味するため、正しくありません。今のところ、状態0に余分な意味を与えていると主張することもできますが、結局のところ、それはあまり重要ではありません。この質問のポイントは、なぜ状態が設定されるのかを知ることです。
この値は、await が例外をキャッチして終了した場合に関係します。結局、また同じawait文に戻ってくることになりますが はいけません。 という状態になってしまいます。これを例で示すのが一番簡単です。私は今2番目のCTPを使っているので、生成されるコードは質問にあるものとは若干異なっています。
以下は、非同期メソッドです。
static async Task<int> FooAsync()
{
var t = new SimpleAwaitable();
for (int i = 0; i < 3; i++)
{
try
{
Console.WriteLine("In Try");
return await t;
}
catch (Exception)
{
Console.WriteLine("Trying again...");
}
}
return 0;
}
概念的には
SimpleAwaitable
は任意の待ち受けにできます。タスクかもしれないし、他の何かかもしれません。私のテストでは、これは常に
IsCompleted
で例外をスローします。
GetResult
.
以下は、生成された
MoveNext
:
public void MoveNext()
{
int returnValue;
try
{
int num3 = state;
if (num3 == 1)
{
goto Label_ContinuationPoint;
}
if (state == -1)
{
return;
}
t = new SimpleAwaitable();
i = 0;
Label_ContinuationPoint:
while (i < 3)
{
// Label_ContinuationPoint: should be here
try
{
num3 = state;
if (num3 != 1)
{
Console.WriteLine("In Try");
awaiter = t.GetAwaiter();
if (!awaiter.IsCompleted)
{
state = 1;
awaiter.OnCompleted(MoveNextDelegate);
return;
}
}
else
{
state = 0;
}
int result = awaiter.GetResult();
awaiter = null;
returnValue = result;
goto Label_ReturnStatement;
}
catch (Exception)
{
Console.WriteLine("Trying again...");
}
i++;
}
returnValue = 0;
}
catch (Exception exception)
{
state = -1;
Builder.SetException(exception);
return;
}
Label_ReturnStatement:
state = -1;
Builder.SetResult(returnValue);
}
移動させなければならなかった
Label_ContinuationPoint
のスコープに含まれないため、有効なコードになりません。
goto
という文がありますが、これは答えに影響しません。
がどうなるか考えてみましょう。
GetResult
は例外を投げます。キャッチブロックを通過して、インクリメントする
i
を使用して、再びループを回します。
i
はまだ3より小さい)。の前と同じ状態です。
GetResult
を呼び出しますが、その中に入ると
try
ブロックは
は
Try"を表示し、次のように呼び出します。
GetAwaiter
また、stateが1でない場合のみ、この処理を行います。
state = 0
を指定すると、既存のアウェイターを使用して
Console.WriteLine
を呼び出します。
しかし、これは、チームが考えなければならないことの一例です。私がこの実装の責任者でなくてよかったです :)
関連
-
[解決済み】指定されたキャストが有効でない?
-
[解決済み】ソケットのアドレス(プロトコル/ネットワークアドレス/ポート)は、通常1つしか使用できない?
-
[解決済み】クロススレッド操作が有効でない。作成されたスレッド以外のスレッドからアクセスされたコントロール
-
[解決済み] [Solved] アセンブリ System.Web.Extensions dll はどこにありますか?
-
[解決済み】なぜこのコードはInvalidOperationExceptionを投げるのですか?
-
[解決済み】MetadataException: 指定されたメタデータ・リソースをロードできない
-
[解決済み] 非同期Task<T>メソッドを同期的に実行するにはどうしたらいいですか?
-
[解決済み] ロックステートメントのボディ内で 'await' 演算子を使用できないのはなぜですか?
-
[解決済み] C#でawaitを使わずに非同期メソッドを安全に呼び出す方法
-
[解決済み] await/asyncを使用しているときにHttpClient.GetAsync(...)が返らない
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】「未割り当てのローカル変数を使用」とはどういう意味ですか?
-
[解決済み】スクリプトクラスが見つからないので、スクリプトコンポーネントを追加できない?
-
[解決済み】パディングが無効で、削除できない?
-
[解決済み] 'SubSonic.Schema .DatabaseColumn' 型のオブジェクトをシリアライズする際に、循環参照が検出されました。
-
[解決済み】トランスポート接続からデータを読み取れない:既存の接続は、リモートホストによって強制的に閉じられました。
-
[解決済み】Entity FrameworkからのSqlException - セッション内で他のスレッドが動作しているため、新しいトランザクションは許可されません。
-
[解決済み】C#のequal to演算子でtextとvarcharのデータ型は互換性がない
-
[解決済み】2つ(またはそれ以上)のリストを1つに統合する(C# .NETで
-
[解決済み】2年前のMSDateを把握する【クローズド
-
[解決済み】Unityでゲームオブジェクトのすべての子をループスルーして破壊する方法?