「Ready!」の後に「Finished」が表示される理由 これは、非同期メソッドとの一般的な混乱点のためであり、SynchronizationContextsとは何の関係もありません。彼らはどのスレッドで実行されるかをSynchronizationContextで制御しますが、「async」には順序付けに関して独自の非常に具体的なルールがあります。そうでなければ、プログラムはおかしくなります!:)
'await'は、現在のasyncメソッドの残りのコードが、待機が完了するまで実行されないようにします。発信者については何も約束しません。
非同期メソッドは「void」を返します。これは、元の呼び出し元がメソッドの完了に依存することを許可しない非同期メソッドを対象としています。呼び出し元も待機させたい場合は、非同期メソッドがTask
(完了/例外のみを監視する場合)、またはTask<T>
実際に値も返したい場合は、を返すようにする必要があります。メソッドの戻り型がこれら2つのいずれかであると宣言すると、コンパイラーが残りの処理を行い、そのメソッド呼び出しを表すタスクを生成します。
例えば:
static void Main(string[] args)
{
Console.WriteLine("A");
// in .NET, Main() must be 'void', and the program terminates after
// Main() returns. Thus we have to do an old fashioned Wait() here.
OuterAsync().Wait();
Console.WriteLine("K");
Console.ReadKey();
}
static async Task OuterAsync()
{
Console.WriteLine("B");
await MiddleAsync();
Console.WriteLine("J");
}
static async Task MiddleAsync()
{
Console.WriteLine("C");
await InnerAsync();
Console.WriteLine("I");
}
static async Task InnerAsync()
{
Console.WriteLine("D");
await DoSomething();
Console.WriteLine("H");
}
private static Task DoSomething()
{
Console.WriteLine("E");
return Task.Run(() =>
{
Console.WriteLine("F");
for (int i = 1; i < 10; i++)
{
Thread.Sleep(100);
}
Console.WriteLine("G");
});
}
上記のコードでは、「A」から「K」の順に印刷されます。何が起こっているのか:
「A」:他の何かが呼び出される前に
「B」:OuterAsync()が呼び出されていますが、Main()はまだ待機中です。
「C」:MiddleAsync()が呼び出されていますが、OuterAsync()は、MiddleAsync()が完了しているかどうかを確認するために待機しています。
「D」:InnerAsync()が呼び出されていますが、MiddleAsync()は、InnerAsync()が完了しているかどうかを確認するために待機しています。
「E」:DoSomething()が呼び出されていますが、InnerAsync()は、DoSomething()が完了しているかどうかを確認するために待機しています。並行して開始するタスクをすぐに返します。
並列処理のため、InnerAsync()がDoSomething()によって返されるタスクの完全性のテストを終了してから、DoSomething()タスクが実際に開始するまでの間に競合が発生します。
DoSomething()が起動すると、「F」が出力され、1秒間スリープします。
その間、スレッドスケジューリングがめちゃくちゃになっていない限り、InnerAsync()は、DoSomething()がまだ完了していないことをほぼ確実に認識しています。これで非同期マジックが始まります。
InnerAsync()は、それ自体をコールスタックからヤンクし、そのタスクが不完全であることを示します。これにより、MiddleAsync()はそれ自体をコールスタックからヤンクし、それ自体のタスクが不完全であると言います。これにより、OuterAsync()はそれ自体をコールスタックからヤンクし、そのタスクも不完全であると言います。
タスクはMain()に返され、Main()はそれが不完全であることを通知し、Wait()呼び出しが開始されます。
その間...
その並列スレッドで、DoSomething()で作成された古いスタイルのTPLタスクは最終的にスリープを終了します。「G」を印刷します。
そのタスクが完了としてマークされると、残りのInnerAsync()がTPLでスケジュールされて再度実行され、「H」が出力されます。これで、InnerAsync()によって最初に返されたタスクが完了します。
そのタスクが完了とマークされると、残りのMiddleAsync()がTPLでスケジュールされて再度実行され、「I」が出力されます。これで、MiddleAsync()によって最初に返されたタスクが完了します。
そのタスクが完了とマークされると、残りのOuterAsync()がTPLでスケジュールされて再度実行され、「J」が出力されます。これで、OuterAsync()によって最初に返されたタスクが完了します。
OuterAsync()のタスクが完了したので、Wait()呼び出しが返され、Main()は「K」を出力します。
したがって、順序が少し並列化されていても、C#5 asyncは、コンソールの書き込みがその正確な順序で行われることを保証します。
これがまだ混乱しているように思われる場合はお知らせください:)