await Task.Run(() => HeavyMethod());
このようなサンプル良く見かけますね。何気なく使っていました。
実はこれで失敗したんです。
私は最近までTask.Runで包み込んでやると上記サンプルのようにHeavyMethodもTaskになるものだと勘違いしていました。
後から調べたらHeavyMethodの中身が問題で、awaitを使う場合async voidはUIのイベント処理以外は使うなと色々な所に書かれていますね。
例えばコマンド(Start)を送った後2秒待っでコマンド(End)を送る処理で、Start~Endが終わってから次の処理を実行することが条件になる場合、async voidを使うと期待通り動かないことがあります。
ご存知の方にはそれほど大げさなことでは無いかも知れませんが...書き留めます。
##async voidでは意図した動作にならない
上記のサンプル物まねでやった結果です。
private async void button1_Click(object sender, EventArgs e)
{
for(var i = 0; i < 3; i++)
{
await Task.Run(() => DelayMethodByAsyncVoid(i));
}
}
async void DelayMethodByAsyncVoid(int id)
{
System.Console.WriteLine("Start "+id);
await Task.Delay(2000);
System.Console.WriteLine("End " + id);
}
Start 0
Start 1
Start 2
End 2
End 1
End 0
ラムダ式の中はTaskですがDelayMethodByAsyncVoid(i)
の中はTaskでは無いのでawait Task.Delay
のところでTask完了となっているみたいです。
DelayMethodByAsyncVoidは非同期で動きます。
そもそもMethod 1つを呼ぶのにTask.Runを使うのが良いのかという疑問もあります。
##ラムダ式の中に処理を書く
ラムダ式の中にDelayMethodByAsync(i)
の処理を書くとすべての処理がTaskとなりうまくいきます。
private async void button2_Click(object sender, EventArgs e)
{
for(var i = 0; i < 3; i++)
{
await Task.Run(async () =>
{
System.Console.WriteLine("Start " + i);
await Task.Delay(2000);
System.Console.WriteLine("End " + i);
});
}
}
Start 0
End 0
Start 1
End 1
Start 2
End 2
###async Taskを定義する
何でもかんでもTask.Runでやっていたのですが複数個所から呼ばれる場合async Taskで定義するのがすっきりします。
private async void button3_Click(object sender, EventArgs e)
{
for(var i = 0; i < 3; i++)
{
await DelayMethodByTask(i);
}
}
async Task DelayMethodByTask(int id)
{
System.Console.WriteLine("Start "+id);
await Task.Delay(2000);
System.Console.WriteLine("End " + id);
}
Start 0
End 0
Start 1
End 1
Start 2
End 2
##動作環境
- 開発環境
- Visual Studio Community 2017
- ターゲットフレームワーク
- .NET Fremawork 4.6.1