C# 初心者が async/await について勉強しているといろいろ分からないことが出てくるので、出来る限りシンプルなコードで確認してみました。
私の理解と疑問点
- 非同期メソッド(async が付いているメソッド)は await で処理を待つことができる
- await を使用しているメソッドには async を付ける必要がある
- await を付けなければ待たないこともできるが、警告が表示される
- だから警告を消すために await をつけ、メソッドには async を付けよう
- 同じ理由で呼び出し元のメソッドでも async/await をつける必要が出てくる
- async/await が連鎖的に伝播していく
- どこで終わればいいの?
① await しない場合
検証コード
namespace AsyncAwaitTest
{
class Program
{
static void Main(string[] args)
{
Run();
Console.WriteLine("[キー入力待ち]");
Console.ReadKey();
}
static void Run()
{
Console.WriteLine("コール前");
Hoge();
Console.WriteLine("コール後");
}
// 何か重い処理を行うメソッド
async static Task<bool> Hoge()
{
Console.WriteLine("Hoge 開始");
await Task.Delay(3000);
Console.WriteLine("Hoge 終了");
return false;
}
}
}
- Hoge() をコールしている箇所で警告が表示される
実行結果
コール前
Hoge 開始
コール後
[キー入力待ち]
Hoge 終了
- await しないので、Hoge をコールした後すぐに制御が戻り「コール後」が出力されている
- Hoge メソッドでは、await した箇所で完了を待ち、3秒経過後に次の処理へ進む
② await する場合
検証コード
namespace AsyncAwaitTest
{
class Program
{
static void Main(string[] args)
{
Run();
Console.WriteLine("[キー入力待ち]");
Console.ReadKey();
}
async static void Run()
{
Console.WriteLine("コール前");
await Hoge();
Console.WriteLine("コール後");
}
// 何か重い処理を行うメソッド
async static Task<bool> Hoge()
{
Console.WriteLine("Hoge 開始");
await Task.Delay(3000);
Console.WriteLine("Hoge 終了");
return false;
}
}
}
- 警告は表示されなくなる
実行結果
コール前
Hoge 開始
[キー入力待ち]
Hoge 終了
コール後
- Run では Hoge を await するので、Hoge をコールした箇所で完了を待つようになる
- Hoge でも await しているので、3秒経過するのを待つ
- 制御が Main に戻る
- 3秒経過後に Hoge の続きの処理が行われる
- Hoge 完了後、Run の続きの処理が行われる
③ await して戻り値を取得する場合
検証コード
namespace AsyncAwaitTest
{
class Program
{
static void Main(string[] args)
{
Run();
Console.WriteLine("[キー入力待ち]");
Console.ReadKey();
}
async static void Run()
{
Console.WriteLine("コール前");
bool ret = await Hoge();
Console.WriteLine("コール後");
}
// 何か重い処理を行うメソッド
async static Task<bool> Hoge()
{
Console.WriteLine("Hoge 開始");
await Task.Delay(3000);
Console.WriteLine("Hoge 終了");
return false;
}
}
}
- 戻り値を取得しているだけで②と同じ
実行結果
②と同じ
結論
async/await が連鎖的に伝播していく
どこで終わればいいの?
に対する答えは async void
のメソッドで終われば良い です。
しかし、async void
はイベントハンドラなどでのみ使用すべきで、通常は、
- 戻り値がない場合は
async Task
- 戻り値がある場合は
async Task<T>
を使用すべきだそうです(参考 URL を参照)。
まとめると、
async void
以外のメソッドは必ずawait
しなければならないawait
しなかったら警告が表示される- でも
async void
は基本的に使用してはダメ async void
は戻り値に取得できない(非同期処理を投げっぱなしで行う)- 唯一の例外がイベントハンドラであり、これは UI スレッドなどを止めないため
- イベントハンドラ以外の処理を
async void
で行うのは、あまり使い道もなくはまりどころになるだけなのでやらないほうがよい - 従って、イベントハンドラまでは async/await を書き続ける必要がある
という感じでしょうか。
同期コンテキストとかの話は難しそうだったので、これから勉強します。