Skip to content

MS_AsynchronousProcessing

nishi_74322014 edited this page Aug 12, 2026 · 1 revision

非同期処理

概要

非同期処理の実装方式について。

詳細

非同期処理の定義

  • 一般的に、非同期処理と言うと、同期処理(オンライン)との対比になり、
    同期処理(オンライン)との待ち合わせをする。

  • 同期処理(オンライン)と待ち合わせをしない場合は、
    同期処理(オンライン)から、

    • 別途スレッドを生成し、そのワーカースレッドで処理させたり、
    • キューにキューイングして、バッチで処理させたり、

    することになるが、対比するものが無いため非同期か?と言うと、
    単なるバッチ処理やマルチスレッド処理、
    若しくは並列処理と言った方がイイかも知れない。

補足(用語の整理): 「非同期」「並行」「並列」は混同されやすい。

用語 意味
非同期 (asynchronous) 呼び出しがすぐ返り、完了は後で通知される。スレッドを増やすとは限らない
並行 (concurrent) 複数の処理が進行中である(時間的に重なる)
並列 (parallel) 複数の処理が同時に実行される(複数コア)

async/await の本質は I/O 待ちの間スレッドを解放することであり、
CPU を使う処理を速くするものではない
CPU バウンドな処理を速くしたいなら Parallel.For /
PLINQ(並列)を使う。ここを取り違えると、
async にしたのに速くならない」という話になる。

非同期処理の実装史

  • プリミティブな実装を隠蔽する方向で進化しているが弊害もある。

1. Thread、ThreadPool

  • 概要

    • 従来の(プリミティブな)、マルチスレッド・プログラミング。
  • 詳細

    • スレッドの並列実行は OS が裏で無意識にしてくれていた。
      • タイムスライスで細切れ/ラウンドロビンで論理的に並列実行。
      • CPU のコア数に応じて、物理的に並列実行。
    • しかし、以下の処理は意識的に実装する必要があった。
      • 非同期処理をスレッド関数として分離して実装する。
      • スレッド関数を作成したワーカースレッドに渡す。
      • (必要に応じて)ワーカースレッドの結果をメインスレッドで待ち合わせる。

2. APM (Beginメソッド, Endメソッド)

  • 概要

    • 前述の Thread、ThreadPool から非同期を意識した API 設計に変更した程度。
    • 代表的なメソッド
      • Stream クラスの、BeginRead および EndRead メソッド
      • WebRequest クラスの、BeginGetResponse および EndGetResponse メソッド
  • 詳細

    • Begin メソッドに End メソッドの delegate(≒ callback)を渡す。
    • 同期方法はクラスの実装に依る。

移行メモ(用語): APM = Asynchronous Programming Model
(非同期プログラミング モデル)。
後述の EAP / TAP と合わせて、Microsoft が公式に定義した
3 つの非同期パターンである。

3. EAP (Control.Invoke、.BeginInvoke)

  • 概要

    • 裏で、ウィンドウ メッセージ(MS_WindowMessage.md)を使用した
      非同期処理方式。
    • Swing の SwingWorker などを見ても GUI の非同期は、
      ほぼ同じ様な仕組みになるもよう。
  • 詳細

    • 前述の APM と同様に、Invoke メソッドに delegate(≒ callback)を渡す。
    • 同期方法はウィンドウ メッセージに依る。

移行メモ(用語と注意): 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 = "完了");

4. Future/Promise

  • 概要

    • C#には無いが、JavaScript の Promise(DNET_JavaScript.md)が有名。
    • その他にも、JavaVM(Java、Scala)、C++ などで
      標準ライブラリが提供されている模様。
  • 詳細

    • 非同期の callback を、delegate を引数に取る method chain に、
      匿名 delegate で実装していく感じ。
    • このコーディング・スタイルにより、
      あまり callback っぽく見えない同期に近いコーディングが可能。
    • しかし、非同期処理の結果から、また非同期を呼び出すようなコードは
      上手く書けない。

移行メモ(正誤): 「C#には無い」は正確ではない。
Task<T> が Future/Promise そのものである。

言語 Future / Promise 相当
JavaScript Promise
Java CompletableFuture
C++ std::future / std::promise
C# Task / Task<T>TaskCompletionSource が Promise 側)

元ページの主旨は「メソッド チェーンで繋ぐスタイルが C# には無い」
ということであろう。実際、C# は .Then().Then() ではなく
await を選んだため、ネストが深くならないという利点を得ている。
JavaScript も後に async/await を導入し、同じ方向に収束した。

5. TAP (async/await)

  • 概要

    • async/await(MS_AsyncAwait.md)で、
      同期呼び出し的に非同期処理を記述可能になった。
    • 斬新な仕組みのため、無批判で浸透したが、
      昨今、振り返りフェーズにある気がする。
  • 詳細

    • 同期方法は同期コンテキストに依存する。

移行メモ(用語): 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 は避けるべきだが)。

6. Reactive Extensions(Rx)

  • 概要

    • Silverlight Toolkit に System.Reactive.dll が同梱されたのを
      きっかけに始まり各言語に広まった。
    • Observer パターンを実装するフレームワークで、
      イベントや非同期処理を LINQ っぽく扱える。
  • 詳細

    • 「状態変化を検知して非同期イベントを発火」のようなライブラリなので、
      業務系にはミスマッチと思われる。ゲーム業界などでは使われている模様。
    • 最近の、非同期呼出というコンテキスト上では、
      async/await が優勢である模様。

補足(Rx の現在地): 「業務系にはミスマッチ」という評価は概ね妥当だが、
使い分けの軸を「値が 1 つか、複数か」に置くと整理しやすい。

単一の値 複数の値(ストリーム)
同期 T IEnumerable<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)が必要な場合に絞られてきている。

参考

SlideShare


Tags: 移行, プログラミング, その他、開発の色々, .NET開発

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally