「マイクロソフト系技術情報 Wiki」は、「Open棟梁Project」,「OSSコンソーシアム .NET開発基盤部会」によって運営されています。
という意味らしい。
主に、非同期処理(≠並列処理)を簡単に(同期処理的に)実装するために導入された。
async/awaitの登場で、非同期処理を同期型処理と、ほぼ変わらない記述で容易に実装可能になった。
しかし、デバッグの時は非同期で実行されていることを意識する必要がある。
(しかも、かなり特殊な。同期コンテキスト毎に動作も大きく異なる。)
async/await]]は、TAP(Task-based Asynchronous Pattern)の方式で実装されている。
async/awaitは、
async/awaitの夫々の意味。
この動作は想像し難いが、具体的には、
というイメージ。
await(Task.Run)を切れ目として、
プログラマが意識して時間のかかるバックグラウンド処理
(ネットワーク バインドまたは I/O バインドの処理)を分割する。
Fire & Forgetの場合、戻り値は不要。
Awaitableの場合、
await Task.FromResult(0); await Task.FromResult(default(object));
Awaitableの場合、
int ret = await XXXXAsync(); return ret;
await Task.FromResult(new T());
Task.FromResult?を使用すると、
詳細については、下記を参照。
ここでは、
組み合わせによって、await後の処理が、
どのように実行されるのかについて説明する。
非同期メソッドには、2種類ある。
非同期メソッドでもreturn文を書く必要が無い。
戻り値がvoidなので非同期以降がフォアグラウンドに復帰しない。
非同期メソッドは、Task、Task<T>をリターンする。
戻り値がTask(もしくはTask<T>)なので、await演算子かWait()メソッド
以降に実装された非同期以降がフォアグラウンドに復帰する。
実行環境によって持つ同期コンテキストの種類が異なる。
GUIアプリケーションでの同期コンテキストは
「Windowsメッセージングキュー(Control.Invoke、.BeginInvoke?で使う)」になる。
Consoleアプリケーションでの同期コンテキストは「null」になる。
Task.Runを使用した場合の同期コンテキストは、
マルチスレッド環境下の「ThreadPool?」になる。
ASP.NETアプリケーションでの同期コンテキストは
マルチスレッド環境下の「System.Threading.SynchronizationContext?.Current」になる。
詳しくは、非同期Controllerを参考にする。
この辺の
スタック・トレースを見ると、
同じ事らしいと解る。
なお、恐らく、これらの同期コンテキストは、
I/O完了ポート(IOCP)= ノンブロッキングI/Oであると思われる。
並列実行を始めるので、
特に、Consoleアプリケーションの同期コンテキスト(null)の下では、
新規に同期処理の実装が必要になることがある。
async/awaitは非同期呼び出しで投げっぱなした後に、
同期コンテキストにより同期される方式のため、同期をあまり考慮していないが、
同期コンテキストによっては、同期を行う必要があるため、以下に注意する。
スレッドを使用した処理を記述しないが、
分割されたタスクは、
このため、一連の処理が(分割されたタスクが)、
従って、スレッド同期のlock等は無意味。
従来のスレッド同期ツールキットは使用不可。
旧プログラムで、
スレッド・アフィニティのないロック機構を使用する。
Win32の待機関数のWaitFor?[Single|Multi]Objectは例外的に使用可。
これらの待機関数は、
待つ関数であるためと考える。
上記の仕組みで動いているとすると、進捗報告をどう実装するかが?であるが、
以下を見ると、Progressクラス、IProgress<T>インターフェースを使用するらしい事が解る。
詳しい仕組みは不明だが、GUI上で動作しているため、
同期コンテキストのControl.Invoke、.BeginInvoke?を使用しているものと思われる。
以下の参考資料から、ガイドラインを纏めてみた。
以下、デッドロックのサンプル。
ざっくり、以下のガイドラインに従う。
詳しくは、下記を参照のこと。
public static async Task FetchFileAsync(int fileNum) { await Task.Run(() => { var contents = IO.DownloadFile(); Console.WriteLine("Fetched file #{0}: {1}", fileNum, contents); }); }
// UIスレッドの同期コンテキストをキャッシュする SynchronizationContext syncContext = SynchronizationContext.Current; //.ConfigureAwait(false)でUIスレッドに戻さない await HeavyWorkAsync().ConfigureAwait(false); // UIスレッドの同期コンテキストにディスパッチする。 syncContext.Post(state => { // 何からの処理。 }, null);
非同期メソッドの呼び出しは次の3つのメモリ確保処理を生む。
◯が付与された、ステートマシンとデリゲートはawaitキーワードが
ランタイムに現れたときに作成されるため、同期処理と比べるとコストになる。