🎼
C# の async/await の挙動で誤解していたこと
C# の非同期処理を勉強している中での勉強メモです。
恐らく、普通に async/await を理解している人にとっては当たり前の内容だと思いますが、触り始めの方の参考になればと思い記事にしてみました🙂
コードは こちらの記事 を参考に、ちょこっと変えています。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncExample01
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
CallTaskAsync();
}
private static async void CallTaskAsync()
{
Console.WriteLine("CallTaskAsync: START");
Task<int> task = Task.Run<int>(new Func<int>(Calculate));
Console.WriteLine("CallTaskAsync: Before");
int result = await task;
Console.WriteLine("CallTaskAsync: After");
Console.WriteLine("CallTaskAsync: END");
}
private static int Calculate()
{
Console.WriteLine("Calculate: START");
int total = 0;
for (int i = 0; i <= 100; ++i)
total += i;
Thread.Sleep(1000);
Console.WriteLine("Calculate: END");
return total;
}
}
}
上記のコードで、こんな風に考えてしまっていました。
「ふむふむ。await
があると非同期処理になって処理が進むから、下のような出力になるのかな?」
Hello World!
CallTaskAsync: START
CallTaskAsync: Before
Calculate: START
CallTaskAsync: After
CallTaskAsync: END
Calculate: END
しかし。得られた出力は下記の通り。
Hello World!
CallTaskAsync: START
CallTaskAsync: Before
Calculate: START
ここでプログラムは終了します。
出力から考えると、int result = await task;
のところに来た後、プログラムごと終わっている感じです。
予想される挙動としては、int result = await task;
の後は CallTaskAsync() の次の行が実行されるのではなく CallTaskAsync()
が一旦リターンして Main()
に戻り、そのまま Main()
が終了したのでプログラムも終了した、という感じでしょうか。
そこで、コードを下記のように書き換えて挙動を確認してみました。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncExample01
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
CallTaskAsync();
Console.WriteLine("Waiting...");
Thread.Sleep(2000);
}
private static async void CallTaskAsync()
{
Console.WriteLine("CallTaskAsync: START");
Task<int> task = Task.Run<int>(new Func<int>(Calculate));
Console.WriteLine("CallTaskAsync: Before");
int result = await task;
Console.WriteLine("CallTaskAsync: After");
Console.WriteLine("CallTaskAsync: END");
}
private static int Calculate()
{
Console.WriteLine("Calculate: START");
int total = 0;
for (int i = 0; i <= 100; ++i)
total += i;
Thread.Sleep(1000);
Console.WriteLine("Calculate: END");
return total;
}
}
}
すると、出力は下記のようになりました。これは想定通りの出力です。
Hello World!
CallTaskAsync: START
CallTaskAsync: Before
Calculate: START
Waiting...
Calculate: END
CallTaskAsync: After
CallTaskAsync: END
ということで、「await 付きの呼び出しをした際は、呼び出し元の関数の処理が続くのではなく、呼び出し元の関数から一度リターンする」 という挙動を覚えておこうと思います。
Discussion