C#:Async/Awaitのベストプラクティス
C# における Async/Await に関するTIPS async void を避ける
async void メソッドよりも async Task メソッドを利用する
例外: イベント ハンドラー
code:cs
// ❌ DO NOT
async void MyMethod()
{
await Task.Delay(1000);
}
// ✅ DO
async Task MyMethodAsync()
{
await Task.Delay(1000);
}
// ✅ DO (UniTask)
async UniTaskVoid MyMethodAsync()
{
await Task.Delay(1000);
}
async void を使う必要のある状況はかなり限定される。async void を使った非同期関数を使用した場合、正しく例外処理が出来なかったり、型の情報を得られなくなったりする
code:cs
private async void ThrowExceptionAsync()
{
throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
try
{
ThrowExceptionAsync();
}
catch (Exception)
{
// ここには到達しない
throw;
}
}
関連
すべて非同期にする
ブロッキングと非同期コードを混在しない
例外: コンソール Main メソッド
非同期処理とブロッキング処理を混在させると、デッドロックが起こったりエラーハンドリングを難しくしたりする。
なんかこう言うのが複雑になる感覚に近そう。
code:ts
const main = () => {
void (async() => {
fs.somethingSync();
try {
await something();
} catch {
// ...
}
// わやじゃ
})()
}
main()
ブロッキング処理を置き換える場合に、使用するべき関数やキーワードは以下
バックグラウンド タスクの結果を取得する
Task.Wait / Task.Result → await
任意のタスクの完了を待機する
Task.WaitAny → await Task.WhenAny (await Promise.any 的なもの)
複数タスクの結果を取得する
Task.WaitAll → await Task.WhenAll (await Promise.all 的なもの)
一定時間待機する
Thread.Sleep →await Task.Delay (await new Promise(resolve => setTimeout(resolve, 3000)) 的なもの)
コンテキストを構成する
可能なときには ConfigureAwait(false) を使用する
例外: コンテキストを必要とするメソッド
await した Task はデフォルトでコンテキストをキャプチャする
コンテキストをキャプチャすると同期ブロックにより処理が遅延する
Task同士の処理の前後関係に影響がなければ Task.ConfigureAwait(false) を指定してキャプチャを抑制するとパフォーマンスが向上する。
関連