--- lang: ja-jp breaks: true --- # C# .NETで非同期ライブラリを正しく実装する 2021-11-20 > .NETで非同期ライブラリを正しく実装する > https://www.infoq.com/jp/articles/Async-API-Design/ > async/await, Taskのタブー② > https://qiita.com/mounntainn/items/3f39e0c57412c48508bf#asyncawait-task%E3%81%AE%E3%82%BF%E3%83%96%E3%83%BC-1 > デッドロック回避法 > https://qiita.com/mounntainn/items/3f39e0c57412c48508bf#%E3%83%87%E3%83%83%E3%83%89%E3%83%AD%E3%83%83%E3%82%AF%E5%9B%9E%E9%81%BF%E6%B3%95 ## ライブラリ内で `Task.Run` を使わない スレッド、特にスレッドプールのスレッドはグローバルに共有されているリソースで、アプリケーション開発者に属しています。ライブラリの作者はTask.Runを使ったり、スレッドを作るメソッドを作成するべきではありません。どのようなタイミングでスレッドを追加するか決めるのはアプリケーション開発者の権利と責任です。 アプリケーションに必要なスレッドプールのリソースが枯渇するかもしれません。ライブラリの開発者は、アプリケーションの開発者と違い、どのくらいスレッドが必要なのかわからないのです。 ## サーバで `Task.Run` を使わない Task.Runはスケーラビリティが求められるサーバでは不適切です。 コア毎に1つのスレッドを走らせるのが理想的です。それ以上のスレッドを立ててしまうとコンテキストのスイッチでCPUのサイクルを無駄にし、メモリ上のスレッドのスタックを無駄にします。 高いスケーラビリティではなく遅延が減少するようにサーバをチューニングするなら、Task.Runを使うのは意味があります。しかし、この決定はアプリケーション開発者がするものです。ライブラリの作者ではありません。 ## クライアントでの `Task.Run` ライブラリでTask.Runを使うと、ライブラリのユーザが最適にスレッドプールを使うのを妨げることになります。 ## `Wait` を使う同期メソッドで非同期メソッドをラップしない キーワードasyncは対象のファンクションが同じコンテキストで実行されるべきであることを示しています。UIスレッドの場合は、実行完了を待つためにディスパッチャが使われるということです。しかし、Task.Waitが呼ばれるとスレッドがブロックされ、そのスレッドはasyncキーワードが終わるまで待つので、処理が返ってこなくなってしまいます。 ライブラリでは基本的にはasyncをブロックするべきではありません。 責任あるライブラリ開発者になる必要があります。メソッドが本当に同期処理を行うなら、非同期バージョンを提供せずに、同期バージョンだけを提供します。同様に本当に非同期だったら、非同期バージョンだけを提供します。 ## デッドロック と `SynchronizationContext` `await` キーワードが使われる場合、その環境での `SynchronizationContext` が捉えられます。その`SynchronizationContext` の `Post` メソッドが呼ばれ、非同期処理が完了すると動作を再開します。`SynchronizationContext` がない場合、継続が `TaskScheduler` に追加されます。 アプリケーションレベルのコードでは、これはほとんど正しい挙動です。しかし、ライブラリではこの動作は間違いを起こします。ライブラリの場合は、次のパターンを利用します。 ```csharp= await FooAsync.ConfigureAwait(false); ``` こうすることで、`SynchronizationContext` を捕まえないようにして、OSが与えたスレッド上で、動作が続くようになります。これにはふたつの利点があります。 * 性能 :スレッドの不必要なマーシャリングがなくなり、性能が改善します。 * ロック:デッドロックが少なくなります。 ライブラリのユーザがUIスレッド上の非同期メソッドで `Task.Wait` が呼ばれてしまうかもしれないということです。この場合、ライブラリ側で `ConfigureAwait(false)` を使わずに、UIスレッド上の処理も先に進もうとすると、デッドロックが発生する可能性があります。 ユーザのスレッドはライブラリの作者ではなくユーザに属しています。ライブラリのコードでユーザのものであるスレッドを汚してはなりません。 つまり、概して言えば、ライブラリは常にタスクを待つときは `ConfigureAwait(false)` を使うべきだ、ということです。 :::info デフォルトでは `ConfigureAwait` は `true` となっており、 `true` の場合は `await` で非同期処理が同期された後の処理が 呼び出し元スレッドのコンテキスト(同期コンテキスト) に スイッチ されます。 ``` ConfigureAwait(true) (デフォルト値) 呼び出し元スレッド:→→→→→→→→→→→→→ 非同期スレッド  :→→↑ await ``` `ConfigureAwait` を `false` に設定した場合には `await` 以降の処理が再度非同期の処理として再開されます。 ``` ConfigureAwait(false) 呼び出し元スレッド:→→→→→→→→→→→→→ 非同期スレッド  :→→↑ await →→→ ``` ::: ###### tags: `C#` `非同期` `sync over async issues` `Task.ConfigureAwait(false)` `SynchronizationContext` `async` `await`