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

目次

概要

用途

主に、非同期処理(≠並列処理)を簡単に(同期処理的に)実装するために導入された。

使い方

async/awaitの登場で、非同期処理を同期型処理と、ほぼ変わらない記述で容易に実装可能になった。

しかし、デバッグの時は非同期で実行されていることを意識する必要がある。
(しかも、かなり特殊な。同期コンテキスト毎に動作も大きく異なる。)

仕組み

実装方法

タスク分割方法

await(Task.Run)を切れ目として、
プログラマが意識して時間のかかるバックグラウンド処理
(ネットワーク バインドまたは I/O バインドの処理)を分割する。

await

Task.Run ~ Task.Wait()

非同期メソッドの戻り値

void型

Fire & Forgetの場合、戻り値は不要。

Task型

Awaitableの場合、

Task<T>型

Awaitableの場合、

Task.FromResult?

Task.FromResult?を使用すると、

詳細については、下記を参照。

非同期メソッドの呼び出し

await演算子

Task.Run()・Task.Wait()、Task.WhenAll?()メソッド

非同期メソッドの作り方

詳細

ここでは、

組み合わせによって、await後の処理が、
どのように実行されるのかについて説明する。

非同期メソッドの種類

非同期メソッドには、2種類ある。

Fire & Forget

非同期メソッドでもreturn文を書く必要が無い。

戻り値がvoidなので非同期以降がフォアグラウンドに復帰しない。

Awaitable

非同期メソッドは、Task、Task<T>をリターンする。

戻り値がTask(もしくはTask<T>)なので、await演算子かWait()メソッド
以降に実装された非同期以降がフォアグラウンドに復帰する。

同期コンテキスト

実行環境によって持つ同期コンテキストの種類が異なる。

GUIアプリケーション

GUIアプリケーションでの同期コンテキストは
「Windowsメッセージングキュー(Control.Invoke、.BeginInvoke?で使う)」になる。

Consoleアプリケーション

Consoleアプリケーションでの同期コンテキストは「null」になる。

ThreadPool?

Task.Runを使用した場合の同期コンテキストは、
マルチスレッド環境下の「ThreadPool?」になる。

ASP.NET

ASP.NETアプリケーションでの同期コンテキストは
マルチスレッド環境下の「System.Threading.SynchronizationContext?.Current」になる。

詳しくは、非同期Controllerを参考にする。

この辺の

スタック・トレースを見ると、

同じ事らしいと解る。

並列実行

並列実行を始めるので、
特に、Consoleアプリケーションの同期コンテキスト(null)の下では、
新規に同期処理の実装が必要になることがある。

同期

async/awaitは非同期呼び出しで投げっぱなした後に、
同期コンテキストにより同期される方式のため、同期をあまり考慮していないが、
同期コンテキストによっては、同期を行う必要があるため、以下に注意する。

仕組みから

スレッドを使用した処理を記述しないが、

分割されたタスクは、

このため、一連の処理が(分割されたタスクが)、

スレッド同期ツールキットは使用不可

従って、スレッド同期のlock等は無意味。
従来のスレッド同期ツールキットは使用不可。

lock/mutex/semaphoreはtaskで全て使用禁止

旧プログラムで、

  1. スレッド・アフィニティのあるロック機構(lock/mutex/semaphore)
    を使用してコードブロックをロックしている場合に、
  2. awaitを使用して追加開発をしたい場合、
    (await演算子を含むコードブロックをロックしたい場合)、

SemaphoreSlim?を使用するように修正が必要になる。

WaitFor?[Single|Multi]Objectは例外的に使用可

Win32の待機関数のWaitFor?[Single|Multi]Objectは例外的に使用可。

これは、これらの待機関数は、

待つ関数であるためと考える。

その他

進捗報告

上記の仕組みで動いているとすると、進捗報告をどう実装するかが?であるが、
以下を見ると、Progressクラス、IProgress<T>インターフェースを使用するらしい事が解る。

詳しい仕組みは不明だが、GUI上で動作しているため、
同期コンテキストのControl.Invoke、.BeginInvoke?を使用しているものと思われる。

ContinueWith?

ConfigureAwait?

ガイドライン

以下の参考資料から、ガイドラインを纏めてみた。

一般的に

戻り値がvoidのメソッドを非同期呼び出ししない。

Task.Wait() を使う場合は注意する

以下、デッドロックのサンプル。

ライブラリの場合

ざっくり、以下のガイドラインに従う。

詳しくは、下記を参照のこと。

ライブラリ内でTask.Runを使わない

Waitを使う同期メソッドで非同期メソッドをラップしない

デッドロックと同期コンテキスト

// UIスレッドの同期コンテキストをキャッシュする
SynchronizationContext syncContext = SynchronizationContext.Current;

//.ConfigureAwait(false)でUIスレッドに戻さない
await HeavyWorkAsync().ConfigureAwait(false);

// UIスレッドの同期コンテキストにディスパッチする。
syncContext.Post(state =>
{
   // 何からの処理。
}, null);

性能についての考察

メモリについての考察

非同期メソッドの呼び出しは次の3つのメモリ確保処理を生む。

◯が付与された、ステートマシンとデリゲートはawaitキーワードが
ランタイムに現れたときに作成されるため、同期処理と比べるとコストになる。

参考

落とし穴

ガイドライン

参考


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS