[[Open棟梁Project>http://opentouryo.osscons.jp/]] - [[マイクロソフト系技術情報 Wiki>http://techinfoofmicrosofttech.osscons.jp/]] * 目次 [#l24f314e] #contents *概要 [#d9ab14bf] awaitの意味は、スレッドを留めずにCallbackを待つということらしい。~ async/awaitはそういう動きをする、非同期プログラムを容易に記述できる。 -async/awaitの登場で、マルチスレッド処理として実装しなくても、~ 非同期処理を同期型処理と、ほぼ変わらない記述で実装可能になった。 -非同期処理はスレッドやWindowsメッセージングキューでTask化される。~ 昔懐かしい、[[ノンプリエンプティブ・マルチタスク]](Win3.1)のようなイメージ。 -しかし、その実態はとても複雑、詳しくは[[コチラ>#nc344611]]を参照。 --内部がどう動いているか詳しく把握せんとシステムは安定しない、 --中身をシッカリ理解して使用している人が少ないので注意。 *非同期処理の実装の歴史 [#z67fe1ac] **Thread、ThreadPool [#c79c02e2] 従来の、マルチスレッド・プログラミング。 -スレッドの並列実行はOSが裏で無意識にしてくれていた。 --タイムスライスで細切れ/ラウンドロビンで論理的に並列実行。 --CPUのコア数に応じて、物理的に並列実行。 -しかし、以下の処理は意識的に実装する必要があった。 --非同期処理をスレッド関数として分離して実装する。 --スレッド関数を作成したワーカースレッドに渡す。 --スレッド関数の結果をメインスレッドで待ち合わせる。 **APM、EAP (Control.Invoke) [#x24ad95f] 「Windowsメッセージングキュー(Control.Invoke)」による方式。 -APM(Asynchronous Programming Model)~ [[Control.Invoke>http://www.atmarkit.co.jp/ait/articles/0506/17/news111.html]] -EAP(Event-based Asynchronous Pattern)~ [[BackgroundWorker>http://www.atmarkit.co.jp/fdotnet/dotnettips/436bgworker/bgworker.html]] Open棟梁の「[[非同期呼出フレームワーク>https://opentouryo.osscons.jp/index.php?%E9%9D%9E%E5%90%8C%E6%9C%9F%E5%91%BC%E5%87%BA%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF]]」がこの方式で実装されている。 **TAP (async/await) [#ae25c1af] async/awaitは、TAP(Task-based Asynchronous Pattern)の方式で実装されている。 ***用途 [#kb0ddc1e] -主に、非同期処理(≠並列処理)を実装するために導入された。 -UIスレッドからのサーバ呼出のハングアップを防止するために使用される。 -内部的には、UIスレッドから、時間のかかるバックグラウンド処理~ (ネットワーク バインドまたは I/O バインドの処理)を分離する。 -この仕組みは、Webサーバのスレッド枯渇を防ぐ[[非同期Controller>ASP.NET MVCの用語#lc319892]]などにも応用されている。 ***使い方 [#l81605a4] -asyncで修飾したTaskを返す非同期メソッドから、awaitステートメントを付与して呼び出す。 -若しくは、Task.Run()で実行して、Task.Wait()で待ち合わせる。 ***タスク分割方法 [#gb4b4431] await(Task.Run)を切れ目として、~ プログラマが意識して時間のかかるバックグラウンド処理~ (ネットワーク バインドまたは I/O バインドの処理)を分割する。 -await --await前の処理はフォアグラウンドで実行される。 --awaitで呼び出す非同期メソッドはバックグラウンドで実行される。 --await後の処理は、コールバックとして実装せずにフォアグラウンドに復帰する。 -Task.Run ~ Task.Wait() --Task.Run前の処理はフォアグラウンドで実行される。 --Task.Run()で呼び出す非同期メソッドはバックグラウンドで実行される。 --Task.Wait()後の処理は、コールバックとして実装せずにフォアグラウンドに復帰する。 ***仕組み [#kaa7ba64] -非同期化は、同期コンテキスト(Windowsメッセージングキュー、スレッド)などを使用して行われる。 --仕組みの詳細については[[コチラ>#nc344611]]を参照。 -並列実行処理を実装するための基盤ではない --[[Fire & Forget>#hc2dffee]]やTask.WhenAllで並列実行処理を実装できるが、~ await、Task.Wait()の待ち合わせまでがセットになっている仕組みなので、~ そもそも、並列実行処理を実装するための基盤ではないことに注意する。~ --並列実行処理を実装する場合は、Threadや、ThreadPoolを使用するようにする。 *使い方 [#nf166e6d] -async/awaitの登場で、同期型処理と、ほぼ変わらない記述で実装可能になった。 -しかし、デバッグの時は非同期で実行されていることを意識する必要がある。 **非同期メソッドの戻り値 [#b43ff41c] ***void型 [#w5cf9ec2] [[Fire & Forget>#hc2dffee]]の場合、戻り値は不要。 ***Task型 [#t98c2e31] [[Awaitable>#v7df61f1]]の場合、 -戻り値の無い非同期メソッドを実装する場合、Task型の戻り値が必要。 -基本的に、returnを書く必要はない。 -しかし、非同期メソッド内で非同期処理を呼び出さない場合、~ 以下のように、完了状態のTaskを生成するreturnを書く必要がある。 await Task.FromResult(0); await Task.FromResult(default(object)); ***Task<T>型 [#td2f6464] [[Awaitable>#v7df61f1]]の場合、 -T型の戻り値の有る非同期メソッドを実装する場合、Task<T>型の戻り値が必要。 -戻り値はTask<T>型だが、awaitした後、Tの型の値をreturnするように実装する。 -しかし、非同期メソッド内で非同期処理を呼び出さない場合、~ 以下のように、完了状態のTask<T>を生成するreturnを書く必要がある。 await Task.FromResult(new T()); ***Task.FromResult [#vfa22973] Task.FromResultを使用して、完了状態のTaskを生成することができる。 詳細については、下記を参照。 -Task.FromResult(TResult) メソッド (TResult) (System.Threading.Tasks)~ https://msdn.microsoft.com/ja-jp/library/hh194922.aspx **非同期メソッドの呼び出し [#s278988c] ***Task.Run()、Task.Wait()メソッド [#l8b4142b] -Task.Run() --非同期メソッドを実行してTask、Task<TResult>を返す。 -Task.Wait() --Task、Task<TResult>の実行が完了するまで待機する。 ***Task.WhenAll() [#j18f876f] -Task.WhenAll()メソッドで複数のタスクを待機するタスクを取得する。 -このTaskをTask.Wait()するとTask毎の例外をAggregateException型として取得可能。 ***await演算子 [#z7b2e8a4] -Task の実行が完了するまで待機する。 --await 非同期メソッド() --await Task.Run() -await演算子の使い方 --非同期メソッドを呼び出すときに、await演算子(後述)を利用する。 --メソッド内でawait演算子(後述)を利用する場合、async修飾子でメソッドを修飾する。 --await演算子は、async修飾子の付くメソッドの中で1つ以上記述できる。 *詳細 [#nc344611] ここでは、 -「非同期メソッドの種類」と -「同期コンテキスト」の 組み合わせによって、await後の処理が、~ どのように実行されるのかについて説明する。 -参考 --async/awaitと同時実行制御 | ++C++; // 未確認飛行 C ブログ~ https://ufcpp.wordpress.com/2012/11/12/asyncawait%E3%81%A8%E5%90%8C%E6%99%82%E5%AE%9F%E8%A1%8C%E5%88%B6%E5%BE%A1/ ---An other world awaits you~ http://www.slideshare.net/ufcpp/an-other-world-awaits-you **非同期メソッドの種類 [#yc35ca64] ***Fire & Forget [#hc2dffee] -非同期呼び出しで投げっぱなす場合。~ 呼び出し元が非同期メソッドの完了を待機する必要がない場合~ (この場合、非同期メソッドでもreturn文を書く必要が無い)。 -具体的には、GUIアプリケーションのイベント・ハンドラに適用する場合。~ (それ以外の用途での利用はハマる原因になるらしいので非推奨らしい) ***Awaitable [#v7df61f1] -非同期呼び出しの呼び出し元でTask.Wait()で待ち合せする場合。~ 呼び出し元が非同期メソッドの完了を待機する必要がある場合~ (この場合、非同期メソッドは、Task、Task<T>をリターンする)。 -上記のGUIアプリケーションのイベント・ハンドラ以外に適用する。 **同期コンテキスト [#je52c46b] 実行環境によって持つ同期コンテキストの種類が異なる。 ***GUIアプリケーション [#yc5c5030] GUIアプリケーションでの同期コンテキストは~ 「Windowsメッセージングキュー(Control.Invoke)」になる。 -Fire & Forget~ GUIアプリケーションのawaitの次の処理は、~ 同期コンテキストのControl.Invokeによって、~ UIスレッド上でシーケンシャルに実行される。 -Awaitable~ 該当なし ***Consoleアプリケーション [#ibd79536] Consoleアプリケーションでの同期コンテキストは「null」になる。 -Fire & Forget~ 非推奨 -Awaitable --Consoleアプリケーション内のawaitの次の処理が実行されるスレッドは一意ではなくなる。 --ただし、処理自体は、シーケンシャルに実行される。 ***ThreadPool [#cf19c6b3] Task.Runを使用した場合の同期コンテキストは、~ マルチスレッド環境下の「ThreadPool」になる。 -Fire & Forget~ 非推奨 -Awaitable --Task.Run内のawaitの次の処理が実行されるスレッドは一意ではなくなり、 --且つ、Task.Run内のawaitの次の処理は、UIスレッドに戻らなくなる。 --ただし、処理自体は、シーケンシャルに実行される。 ***ASP.NET [#s4df3070] ASP.NETアプリケーションでの同期コンテキストは~ マルチスレッド環境下の「System.Threading.SynchronizationContext.Current」になる。 -Fire & Forget~ 非推奨 -Awaitable --ASP.NETアプリケーションのawaitの次の処理が実行されるスレッドは一意ではなくなる。 --ただし、処理自体は、シーケンシャルに実行される。 --最終的に結果は、リクエストを受け付けたスレッドにバインドされる。 詳しくは、[[非同期Controller>ASP.NET MVCの用語#lc319892]]を参考にする。 この辺の -asp.net mvc - MVC Action Filters Collection was modified;~ enumeration operation may not execute - Stack Overflow~ http://stackoverflow.com/questions/29429526/mvc-action-filters-collection-was-modified-enumeration-operation-may-not-execut スタック・トレースを見ると、 -「SynchronizationContext」と -「AsyncControllerActioninvoker」は 同じ事らしいと解る。 ***並列実行 [#k847f44a] -GUI以外の同期コンテキストでFire & Forgetを実行した場合(非推奨)や、 -Task.WhenAllで複数のTaskをTask.Wait()した場合(ThreadPoolで実行される)では、 並列実行を始めるので、 特に、Consoleアプリケーションの同期コンテキスト下では、~ 新規に[[同期処理>#z135f913]]の実装が必要になることがある。 **ConfigureAwait [#vb891159] awaitの挙動は、Awaitableパターンというものを満たすクラスを作ることで、自由にカスタマイズできるらしい。 *スレッド同期 [#w87411f4] async/awaitは非同期呼び出しで投げっぱなす場合に利用されるため、~ スレッド同期をあまり考慮していないが、スレッド同期を行う場合は以下に注意する。 **仕組みから [#ab98fed9] スレッドを使用した処理を記述しないが、分割されたタスクは、 -同一スレッドで動作することも -別スレッドで動作することもある。 このため、一連の処理が(分割されたタスクが)、 -異なるスレッドで実装される保証は無い。 -同じスレッドで実行される保証も無い。 **スレッド同期ツールキットは使用不可 [#sdd9c0ad] 従って、スレッド同期のlock等は無意味。~ 従来のスレッド同期ツールキットは使用不可。 ***lock/mutex/semaphoreはtaskで全て使用禁止 [#offf1b54] -旧プログラムでmutex使ってる場合に、awaitで追加開発したいときは、SemaphoreSlimに修正が必要 -.NET TIPS:非同期:awaitを含むコードをロックするには?(SemaphoreSlim編)[C#、VB] - @IT~ http://www.atmarkit.co.jp/ait/spv/1411/11/news117.html --SemaphoreSlim.WaitAsync関数もasync/awaitする。 --つまり、Semaphoreによるlockさえ、タスクとして分割されて待機/実行される。 ***WaitFor[Single|Multi]Objectは例外的に使用可 [#z135f913] これは、Win32の待機関数のWaitFor[Single|Multi]Objectは、~ スレッドに限らず様々な「オブジェクト」の状態が変化する(シグナル状態になる)のを待つ関数であるためと考える。 -ThreadPool.RegisterWaitForSingleObject メソッド (System.Threading)~ https://msdn.microsoft.com/ja-jp/library/system.threading.threadpool.registerwaitforsingleobject *その他 [#r19de18d] **進捗報告 [#p80858da] 上記の仕組みで動いているとすると、進捗報告をどう実装するかが?であるが、~ 以下を見ると、Progressクラス、IProgress<T>インターフェースを使用するらしい事が解る。 -非同期メソッド - C# によるプログラミング入門 | ++C++; // 未確認飛行 C~ http://ufcpp.net/study/csharp/sp5_async.html#cancel -.NET TIPS:WPF/Windowsフォーム:~ 時間のかかる処理をバックグラウンドで実行するには?(async/await編)[C#/VB] - @IT~ http://www.atmarkit.co.jp/ait/articles/1512/02/news019.html 詳しい仕組みは不明だが、GUI上で動作しているため、~ 同期コンテキストのControl.Invokeを使用しているものと思われる。 *参考 [#l0e9a88c] -Tasks are (still) not threads and async is not parallel~ http://blogs.msdn.com/b/benwilli/archive/2015/09/10/tasks-are-still-not-threads-and-async-is-not-parallel.aspx -c# - Wrapping ManualResetEvent as awaitable task - Stack Overflow~ http://stackoverflow.com/questions/18756354/wrapping-manualresetevent-as-awaitable-task -Insider.NET > 業務アプリInsider > 連載:C# 5.0&VB 11.0新機能「async/await非同期メソッド」入門 -@IT~ http://www.atmarkit.co.jp/ait/subtop/features/dotnet/app/masterasync_index.html