まめ - たんたんめん

備忘録 C# / WPF 多め

C# async / awaitを利用する上での注意点

おはようございます。 今日は C# の非同期構文である async - await について細かい話は色んなブログとかにあったのですが自分用に完結に要点だけまとめます。 構文とか仕組みについてはこの記事では解説しません。

async void 禁止

  • 例外が補足できない
  • 呼び出し元が非同期であることが分かりづらい
  • どうしても fire and for get したい場合呼び出し側で引数を破棄するなり対応した方がよいです。
_ = HogeAsync(); // これで戻り値を破棄できる
  • eventの場合は使わざるを得ないケースもあるが、それ以外は無い。

コンストラクタでの非同期は避ける

  • ↑に通じますが、コンストラクタではawaitできません。そう書きたくなったら設計を見直したほうが堅実

Wait() Resultの使用禁止

  • 主にGUIツール等、スレッドを復帰するタスクが含まれている場合呼び出し元のスレッドの終了を待つことになりデッドロックする

可能な限りConfigureAwait(false)を

  • スレッドを復帰する必要がない場合はfalseにした方がパフォーマンスが向上する
  • 正し処理内容による。特に待たない場合は変数

そのTask、ValueTaskで良いかも

  • 非同期にならない戻り値パスがある場合は ValueTaskを利用することでアロケーションが抑えられる
  • 標準ではTask.WhenAllでValueTaskは待てないのでそこは注意が必要

タスクを直接returnする場合 async / await は書かない

  • メソッドをasyncにするだけでわずかにコストがかかる

参考 (https://gist.github.com/pierre3/10731634)

// これなら
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スレッドにディスパッチして通知等)
              }
          });