Async/Awaitは、C# 5で導入された、間違いなく最も強力な言語フィーチャである。しかし、どのような落とし穴を避けなければならないのか?そして、これらのキーワードに関連したコストは、どうなのか?
MSDNの記事 “Best Practices in Asynchronous Programming” は、以下の点を強調している –
- Event ハンドラーを除いて、非同期のvoidメソッドよりも非同期のTaskメソッドを使う。それらは異なるエラーハンドリングセマンティックを持つ。Marker MetroのKeith Patton氏もこれを詳細に説明している。
- ブロッキングとノンブロッキングのコードを混ぜるのを避ける – 混ぜるとデッドロック, もっと複雑なエラー処理、コンテキストスレッドの予期せねブロックの原因になり得る。
- もし継続コードが元のコンテキストを必要としないなら、パフォーマンスを向上させるためにConfigureAwait(false)を使用する。– それは、スレッドプールのコンテキストで継続を実行する。これはまた、同期および非同期コードを混在させざるを得ない場合にデッドロックを回避するのに役立つ。サーバー側で使用した場合には、これには若干異なる配慮 があることに注意する。
RedGateのソフトウェアエンジニアであるChris Hurley氏は、async-awaitのCPU オーバーヘッドを説明し、サンプルコードをプロファイリングして同じ事を示している。–
- “async”キーワードを伴うメソッドを呼び出すことは、ステートマシンを生成し、Taskを作り、その中で動き続ける作業を持ち、その実行コンテキストと同期コンテキストを取得する。
- 示された例では、963のフレームワークメソッドが最初の呼び出しにおける比較的単純な非同期メソッドを初期化するために実行された。
- コンテキストがキャッシュされているので、それ以降の呼び出しでは、オーバーヘッドがずっと小さい。
- 非常に短い時間間隔(例えば1ms)で同期的に走るメソッドに対して、非同期オーバーヘッドは、呼び出し元のスレッドを実際にはもっと長くブロックする。例では、呼び出し元のスレッドがブロック解除されるまで45msもかかっていた。ループで実行する場合でも、後続の呼び出しのオーバーヘッドが小さくなったにもかかわらず、呼び出し元のスレッドはパフォーマンス上の利益を全く得ることができない。
- 結論 – 非常に短いメソッドにasync/awaitを使うことを避ける、あるいはきついループ内でawaitステートメントを持つことをを避ける(代わりに非同期メソッドにループ全体を置く)
我々はこれらのキーワードを使う時の他の共通の落とし穴について既に扱った。