C# async / awaitを利用する上での注意点
おはようございます。 今日は C# の非同期構文である async - await について細かい話は色んなブログとかにあったのですが自分用に完結に要点だけまとめます。 構文とか仕組みについてはこの記事では解説しません。
- async void 禁止
- コンストラクタでの非同期は避ける
- Wait() Resultの使用禁止
- 可能な限りConfigureAwait(false)を
- そのTask、ValueTaskで良いかも
- タスクを直接returnする場合 async / await は書かない
- 並列処理できる場合は場合は Task.WhenAllで
- なるべくTask.Runでラップしただけの偽asyncメソッドは避ける
- fire and for get する場合はContinueWithで例外捕捉を
async void 禁止
- 例外が補足できない
- 呼び出し元が非同期であることが分かりづらい
- どうしても fire and for get したい場合呼び出し側で引数を破棄するなり対応した方がよいです。
_ = HogeAsync(); // これで戻り値を破棄できる
- eventの場合は使わざるを得ないケースもあるが、それ以外は無い。
コンストラクタでの非同期は避ける
- ↑に通じますが、コンストラクタではawaitできません。そう書きたくなったら設計を見直したほうが堅実
Wait() Resultの使用禁止
可能な限りConfigureAwait(false)を
- スレッドを復帰する必要がない場合はfalseにした方がパフォーマンスが向上する
- 正し処理内容による。特に待たない場合は変数
そのTask、ValueTaskで良いかも
- 非同期にならない戻り値パスがある場合は ValueTask
を利用することでアロケーションが抑えられる - 標準ではTask.WhenAllでValueTaskは待てないのでそこは注意が必要
タスクを直接returnする場合 async / await は書かない
- メソッドをasyncにするだけでわずかにコストがかかる
// これなら public async Task HogeAsync() { await FugaAsync(); } // こう書く public Task HogeAsync() { return FugaAsync() }
並列処理できる場合は場合は Task.WhenAllで
// これなら using (var client = new HttpClient()) { var result1 = await client.GetAsync(@"http://Hoge.example.com"); var result2 = await client.GetAsync(@"http://Fuga.example.com"); ... } // こう書く using (var client = new HttpClient()) { var result1 = client.GetAsync(@"http://Hoge.example.com"); var result2 = client.GetAsync(@"http://Fuga.example.com"); await Task.WhenAll(result1,result2); ... }
なるべくTask.Runでラップしただけの偽asyncメソッドは避ける
参考 C# asyncでやってはいけないこと · GitHub - ポリシー的な話だが、スレッド制御はなるべくアプリケーションに近いレイヤーで行うことを意識すべし
fire and for get する場合はContinueWithで例外捕捉を
- 未処理例外になり、アプリケーションで補足できない
_ DoAsync() .ContinueWith(t => { if (t.Exception != null) { // 例外処理をここでする (ロギング、UIスレッドにディスパッチして通知等) } });