🔥

C#の非同期処理で勘違いしていたこと

に公開

こんにちは!個人開発をしているしがないエンジニアです。

https://loglee.warikan-app211.workers.dev/

最近、バックエンドとして C#を初めて触り、非同期の async / await について、どうしても納得できないことがあったのですが、やっとなんとなくのイメージが掴めました。それについて簡単に記載していきます。
イメージの話で、厳密な解説ではありませんのでご了承ください。

結論

「非同期」という言葉のイメージから勝手に勘違いしていたのは、 「I/O 処理やネットワーク通信など、時間がかかる処理を await によって裏で動かし、その間に、後続に書いてある処理が実行できる」 というイメージでした。

しかし、デバッグで実行してみると、await の箇所で処理が止まり、応答が返ってくるまで次の行に進まないため、「非同期なのに、記載の順番通りに実行され、await でしっかり待つのがおかしい。非同期なら、その後の処理も可能なところまで先に実行してほしい。これならただの同期処理では?」と疑問に思っていました。

いろいろ調べてやっとわかった挙動のイメージは、「await で待っている間(I/O などの待ち時間中)に、呼び出し元のスレッドを、異なる別の処理に使えるようにする」ということでした。

自分が思っていた勘違い

「非同期」という言葉から、await で待っている結果を次に使うまで、その間に書かれている処理を実行できると思っていました。

// 勘違いしていたコード
var result = await UploadDataAsync(); // ここの重い処理を裏で実行する
Console.WriteLine("次の作業"); // ここは上の結果を待たずに実行される
var status = result.Status; // ここはawaitで待っているので、上の結果が返ってくるまで進まない

実際には、await がついている行で、処理が終わるまで完全に待機する動きになります。
つまり、UploadDataAsync の処理が完了して結果が返ってくるまで、次の Console.WriteLine には進みません。
コードは書かれた順番通りに、上から下へ実行されます。

「それだと同期処理と同じでは?何の意味があるの?」となりますが、ここで重要なのが 「待っている間、このスレッドは何をしているか?」 という点です。

本来の「非同期」のメリット

await の本質は、「待っている間のスレッドの有効活用」にあります。

// 1. UploadDataAsync を呼び出し、await で待つ間、スレッドを解放して待機状態になる。
// (処理はここで完全にストップし、下のコードには進まない)
var result = await UploadDataAsync();

// 2. 待っている間、スレッドは解放されるので、他の処理(別の人のリクエストなど)を捌ける!
// (これが非同期のメリット)

// 3. 処理が完了して戻ってきたら、スレッドが再割り当てされて、後続のコードが動く。
Console.WriteLine("次の作業");
var status = result.Status;

デバッグ上で見ると「ここで止まっている」ように見えますが、その裏でスレッドは解放され、他のリクエスト処理など、別の仕事をこなせます。これが「非同期」のメリットでした。
特に、フロントエンドでは、処理待ちで UI が止まってしまわないように非同期にする必要があります。

※自分はバックエンドのみで C#を使っているため、非同期で UI の処理を止めないようにする必要がない。というのが、非同期を理解しづらかった原因かもしれません。

まとめ

  • 勘違い: 非同期だから、await の行を飛び越えて、後続の処理を勝手に進めてくれるはず。
  • 実際: await はしっかり待つ。その代わり、待っている間に「スレッド」というリソースを他の作業に割り当てることができる。

補足

もし、重い処理などを本当に別の場所(別スレッド)で動かしてメインスレッドを止めないようにしたい場合は、明示的に Task.Run を使うと実現可能なようです。

// これなら「別の場所(スレッド)」で動く
await Task.Run(() => UploadDataAsync());

Discussion