目次
→https://qiita.com/tera1707/items/4fda73d86eded283ec4f
やりたいこと
Taskを使って、重くて時間がかかる処理があっても、UIを固めずに済ませたい。
概要
- "Task"を使う。
- Taskは、スレッドプールを簡単に使えるようにしたもの(参考)。
- Taskは、.NET Framework 4 以降で追加された。
- .NET Framework 4 以前は、Threadクラスを使ってスレッド、スレッドプールを使ってマルチスレッドを行っていたが、4以降はTaskを使うべき。
- 並列処理をするには、ほかにParallelクラスやParallelEnumerableクラスというのもあるらしい。(未検証)
Taskについて
こちらで、非常にわかりやすい説明をして頂いている。
ので、詳しい内容はそちらを参考として、理解を確かめる際に作ったサンプルを備忘録代わりに挙げておく。
例
基本の形
Task.Run()で、処理をスタートしつつ、Taskの戻り値を受け、それをTask.WhenAllで使って処理が終わるまで待つ。
// 基本
private async void Button_Click(object sender, RoutedEventArgs e)
{
var task1 = Task.Run(() =>
{
Thread.Sleep(3000);
Debug.WriteLine("task1 完了");
});
var task2 = Task.Run(() =>
{
Thread.Sleep(1000);
Debug.WriteLine("task2 完了");
});
var task3 = Task.Run(() =>
{
Thread.Sleep(2000);
Debug.WriteLine("task3 完了");
});
await Task.WhenAll(task1, task2, task3);
Debug.WriteLine("task1,2,3 完了");
}
実験
どうなるのか?と思って試したコード。
Task.Run()で開始したタスクが、Task.WhenAll()の前に終わっていたらどうなる?
⇒結果、Task.WhenAll()を即通過した。
// 重い処理の代わりのメソッド
private void LongWaitingMethod(int millisec)
{
Thread.Sleep(millisec);
Debug.WriteLine("LongWaitingMethod " + millisec + " 完了");
}
// Task.WhenAllの前に、すでに全部のtaskが終わっている
private async void Button_Click_2(object sender, RoutedEventArgs e)
{
var task1 = Task.Run(() => LongWaitingMethod(3000)); // task1 開始
var task2 = Task.Run(() => LongWaitingMethod(1000)); // task2 開始
var task3 = Task.Run(() => LongWaitingMethod(2000)); // task3 開始
// 5秒ここで待つ(ブロック)
LongWaitingMethod(5000);
Debug.WriteLine("5秒待ち 終わり");
// task1-3はすでに完了しているので、ここは即抜ける
await Task.WhenAll(task1, task2, task3);
Debug.WriteLine("task1,2,3 完了");
}
戻り値を返すTask
Taskの処理から、戻り値を受け取ることができる。
WhenAll()に渡したTaskすべてから受け取れる。
// 重い処理の代わりのメソッド(文字列を戻り値で返す)
private string LongWaitingMethodReturnString(int millisec)
{
Thread.Sleep(millisec);
Debug.WriteLine("LongWaitingMethod " + millisec + " 完了");
return "LongWaitingMethod " + millisec + " 完了";
}
// 戻り値ありのTask(全部同じ型の戻り値)
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
var task1 = Task<string>.Run(() => LongWaitingMethodReturnString(3000)); // task1 開始
var task2 = Task<string>.Run(() => LongWaitingMethodReturnString(1000)); // task2 開始
var task3 = Task<string>.Run(() => LongWaitingMethodReturnString(2000)); // task3 開始
string[] ret = await Task.WhenAll(task1, task2, task3);
Debug.WriteLine("task1,2,3 完了");
// 戻り値
Debug.WriteLine("戻り値1:" + ret[0]);
Debug.WriteLine("戻り値2:" + ret[1]);
Debug.WriteLine("戻り値3:" + ret[2]);
}
異なった型の戻り値を返すTaskをWhenAll
WhenAllで待つTaskが別々の戻り値の型を持つ場合でも、戻り値を受け取れる。
Taskが完了した後に、Resultプロパティを見る。
// 戻り値ありのTask(全部異なる型の戻り値)
private async void Button_Click_4(object sender, RoutedEventArgs e)
{
var task1 = Task<string>.Run(() => { Thread.Sleep(3000); return "task1"; }); // task1 開始
var task2 = Task<int>.Run(() => { Thread.Sleep(1000); return 2; }); // task2 開始
var task3 = Task<double>.Run(() => { Thread.Sleep(2000); return 3.33; }); // task3 開始
await Task.WhenAll(task1, task2, task3);
Debug.WriteLine("task1,2,3 完了");
// 戻り値
Debug.WriteLine("戻り値1:" + task1.Result);
Debug.WriteLine("戻り値2:" + task2.Result);
Debug.WriteLine("戻り値3:" + task3.Result);
}
処理が終わる前にResultを見ると、task1.Wait()と同じ動きをする。(ここでブロックがかかるため、UIも固まるので注意)↓↓↓↓↓↓
// .Resultで、処理が終わるまでUIフリーズする例
private async void Button_Click_4(object sender, RoutedEventArgs e)
{
var task1 = Task<string>.Run(() => { Thread.Sleep(5000); return "task1"; }); // task1 開始
var task2 = Task<int>.Run(() => { Thread.Sleep(1000); return 2; }); // task2 開始
var task3 = Task<double>.Run(() => { Thread.Sleep(2000); return 3.33; }); // task3 開始
// 戻り値
Debug.WriteLine("戻り値1:" + task1.Result); // ここでブロックかかり、task1が終わるまでの5秒間UIフリーズ
Debug.WriteLine("戻り値2:" + task2.Result);
Debug.WriteLine("戻り値3:" + task3.Result);
await Task.WhenAll(task1, task2, task3);
Debug.WriteLine("task1,2,3 完了");
}
TaskのWaitをするときは注意
こちらに書かれている通り、Waitをするときは注意。
Task内でawaitした後、Taskを呼んだ側でそのTaskをWaitしてしまうと、デッドロックする。
どうしてそうなるか?は、こちらの解説が分かりやすい。
Task.Run()したとき、awaitしたときのスレッドの動きが図解されている。
参考
Taskを極めろ!async/await完全攻略
https://qiita.com/acple@github/items/8f63aacb13de9954c5da
++C++; // 未確認飛行 C
https://ufcpp.net/study/csharp/sp_thread.html
コード