-
Notifications
You must be signed in to change notification settings - Fork 0
MS_AsynchronousProcessing
非同期処理の実装方式について。
-
一般的に、非同期処理と言うと、同期処理(オンライン)との対比になり、
同期処理(オンライン)との待ち合わせをする。 -
同期処理(オンライン)と待ち合わせをしない場合は、
同期処理(オンライン)から、- 別途スレッドを生成し、そのワーカースレッドで処理させたり、
- キューにキューイングして、バッチで処理させたり、
することになるが、対比するものが無いため非同期か?と言うと、
単なるバッチ処理やマルチスレッド処理、
若しくは並列処理と言った方がイイかも知れない。
補足(用語の整理): 「非同期」「並行」「並列」は混同されやすい。
用語 意味 非同期 (asynchronous) 呼び出しがすぐ返り、完了は後で通知される。スレッドを増やすとは限らない 並行 (concurrent) 複数の処理が進行中である(時間的に重なる) 並列 (parallel) 複数の処理が同時に実行される(複数コア)
async/awaitの本質は I/O 待ちの間スレッドを解放することであり、
CPU を使う処理を速くするものではない。
CPU バウンドな処理を速くしたいならParallel.For/
PLINQ(並列)を使う。ここを取り違えると、
「asyncにしたのに速くならない」という話になる。
- プリミティブな実装を隠蔽する方向で進化しているが弊害もある。
-
概要
- 従来の(プリミティブな)、マルチスレッド・プログラミング。
-
詳細
- スレッドの並列実行は OS が裏で無意識にしてくれていた。
- タイムスライスで細切れ/ラウンドロビンで論理的に並列実行。
- CPU のコア数に応じて、物理的に並列実行。
- しかし、以下の処理は意識的に実装する必要があった。
- 非同期処理をスレッド関数として分離して実装する。
- スレッド関数を作成したワーカースレッドに渡す。
- (必要に応じて)ワーカースレッドの結果をメインスレッドで待ち合わせる。
- スレッドの並列実行は OS が裏で無意識にしてくれていた。
-
概要
- 前述の Thread、ThreadPool から非同期を意識した API 設計に変更した程度。
- 代表的なメソッド
-
Streamクラスの、BeginReadおよびEndReadメソッド -
WebRequestクラスの、BeginGetResponseおよびEndGetResponseメソッド
-
-
詳細
-
BeginメソッドにEndメソッドの delegate(≒ callback)を渡す。 - 同期方法はクラスの実装に依る。
-
移行メモ(用語): APM = Asynchronous Programming Model
(非同期プログラミング モデル)。
後述の EAP / TAP と合わせて、Microsoft が公式に定義した
3 つの非同期パターンである。
-
概要
- 裏で、ウィンドウ メッセージ(
MS_WindowMessage.md)を使用した
非同期処理方式。 - Swing の
SwingWorkerなどを見ても GUI の非同期は、
ほぼ同じ様な仕組みになるもよう。
- 裏で、ウィンドウ メッセージ(
-
詳細
- 前述の APM と同様に、
Invokeメソッドに delegate(≒ callback)を渡す。 - 同期方法はウィンドウ メッセージに依る。
- 前述の APM と同様に、
移行メモ(用語と注意): EAP = Event-based Asynchronous Pattern
(イベント ベースの非同期パターン)。BackgroundWorkerや
WebClient.DownloadStringAsync+DownloadStringCompletedが典型例。なお、
Control.Invoke/BeginInvokeは EAP そのものというより、
**「UI スレッドへのマーシャリング」**の仕組みである。
Windows Forms / WPFでは
UI 要素にアクセスできるのは UI スレッドだけという制約があり、
ワーカースレッドから UI を触るとこれで戻す必要がある。// Windows Forms this.Invoke(() => label1.Text = "完了"); // WPF Dispatcher.Invoke(() => label1.Content = "完了");
-
概要
-
C#には無いが、JavaScript の Promise(
DNET_JavaScript.md)が有名。 - その他にも、JavaVM(Java、Scala)、C++ などで
標準ライブラリが提供されている模様。
-
C#には無いが、JavaScript の Promise(
-
詳細
- 非同期の callback を、delegate を引数に取る method chain に、
匿名 delegate で実装していく感じ。 - このコーディング・スタイルにより、
あまり callback っぽく見えない同期に近いコーディングが可能。 - しかし、非同期処理の結果から、また非同期を呼び出すようなコードは
上手く書けない。
- 非同期の callback を、delegate を引数に取る method chain に、
移行メモ(正誤): 「C#には無い」は正確ではない。
Task<T>が Future/Promise そのものである。
言語 Future / Promise 相当 JavaScript PromiseJava CompletableFutureC++ std::future/std::promiseC# Task/Task<T>(TaskCompletionSourceが Promise 側)元ページの主旨は「メソッド チェーンで繋ぐスタイルが C# には無い」
ということであろう。実際、C# は.Then().Then()ではなく
awaitを選んだため、ネストが深くならないという利点を得ている。
JavaScript も後にasync/awaitを導入し、同じ方向に収束した。
-
概要
- async/await(
MS_AsyncAwait.md)で、
同期呼び出し的に非同期処理を記述可能になった。 - 斬新な仕組みのため、無批判で浸透したが、
昨今、振り返りフェーズにある気がする。
- async/await(
-
詳細
- 同期方法は同期コンテキストに依存する。
移行メモ(用語): TAP = Task-based Asynchronous Pattern
(タスク ベースの非同期パターン)。
現在の .NET における唯一の推奨パターンであり、
新規実装で APM / EAP を選ぶ理由は無い。
補足(「振り返りフェーズ」の中身): 「無批判で浸透した」という
指摘は的確で、実際に次のような問題が知られている。
問題 内容 asyncの伝染一箇所 asyncにすると呼び出し元まで全部asyncになる("async all the way")デッドロック 同期コンテキストのある環境(旧 ASP.NET / WinForms)で .Result/.Wait()を呼ぶと固まるasync void例外を捕捉できずプロセスが落ちる。イベント ハンドラ以外で使ってはならない 性能 ステート マシンとアロケーションのコスト。ホット パスでは ValueTaskを検討特に デッドロックは典型的で、
// NG: UI スレッド / 旧 ASP.NET で固まる var result = SomeAsyncMethod().Result;これは「UI スレッドに戻ろうとする継続」と「UI スレッドの待機」が
相互に待つために起きる。
ライブラリ側でConfigureAwait(false)を付ける、
呼び出し側で最後までawaitする、のいずれかで避ける。なお ASP.NET Coreには同期コンテキストが無いため、
このデッドロックは起きない(それでも.Resultは避けるべきだが)。
-
概要
- Silverlight Toolkit に
System.Reactive.dllが同梱されたのを
きっかけに始まり各言語に広まった。 - Observer パターンを実装するフレームワークで、
イベントや非同期処理を LINQ っぽく扱える。
- Silverlight Toolkit に
-
詳細
- 「状態変化を検知して非同期イベントを発火」のようなライブラリなので、
業務系にはミスマッチと思われる。ゲーム業界などでは使われている模様。 - 最近の、非同期呼出というコンテキスト上では、
async/await が優勢である模様。
- 「状態変化を検知して非同期イベントを発火」のようなライブラリなので、
補足(Rx の現在地): 「業務系にはミスマッチ」という評価は概ね妥当だが、
使い分けの軸を「値が 1 つか、複数か」に置くと整理しやすい。
単一の値 複数の値(ストリーム) 同期 TIEnumerable<T>非同期 Task<T>(async/await)IObservable<T>(Rx)IAsyncEnumerable<T>(C# 8 以降)つまり Rx と async/await は競合ではなく守備範囲が違う。
「1 回だけ結果が返る」ならTask、
「イベントが流れ続ける」なら Rx かIAsyncEnumerable。C# 8 の
IAsyncEnumerable<T>+await foreachの登場により、
「単純な非同期ストリーム」は標準機能で書けるようになった。
Rx を選ぶのは、時間軸の演算子(Throttle/Debounce/
Buffer/CombineLatest)が必要な場合に絞られてきている。
-
非同期プログラミングのパターン | Microsoft Learn
https://learn.microsoft.com/dotnet/standard/asynchronous-programming-patterns/ -
タスク ベースの非同期パターン (TAP)
https://learn.microsoft.com/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap -
非同期ストリーム (IAsyncEnumerable)
https://learn.microsoft.com/dotnet/csharp/asynchronous-programming/generate-consume-asynchronous-stream -
.NET開発における非同期処理の基礎と歴史 - @IT
http://www.atmarkit.co.jp/fdotnet/chushin/masterasync_01/masterasync_01_02.html
- C#次世代非同期処理概観 - Task vs Reactive Extensions
http://www.slideshare.net/neuecc/asynchronous-rx-andtask - async/await のしくみ
https://www.slideshare.net/ufcpp/asyncawait-114647813
Tags: 移行, プログラミング, その他、開発の色々, .NET開発
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。