【C#】【Task】Taskを使って並行処理(並列処理？)をする・UIを固めずに重い処理をする

**目次**
→https://qiita.com/tera1707/items/4fda73d86eded283ec4f

# やりたいこと
Taskを使って、重くて時間がかかる処理があっても、UIを固めずに済ませたい。

# 概要
- "Task"を使う。
- Taskは、スレッドプールを簡単に使えるようにしたもの([参考](https://ufcpp.net/study/csharp/sp_thread.html))。
- Taskは、.NET Framework 4 以降で追加された。
- .NET Framework 4 以前は、Threadクラスを使ってスレッド、スレッドプールを使ってマルチスレッドを行っていたが、4以降はTaskを使うべき。
- 並列処理をするには、ほかにParallelクラスやParallelEnumerableクラスというのもあるらしい。(未検証)

# Taskについて
[こちら](https://qiita.com/acple@github/items/8f63aacb13de9954c5da)で、非常にわかりやすい説明をして頂いている。
ので、詳しい内容は[そちら](https://qiita.com/acple@github/items/8f63aacb13de9954c5da)を参考として、理解を確かめる際に作ったサンプルを備忘録代わりに挙げておく。

# 例
### 基本の形
Task.Run()で、処理をスタートしつつ、Taskの戻り値を受け、それをTask.WhenAllで使って処理が終わるまで待つ。

```cs
// 基本
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()を即通過した。

```cs
// 重い処理の代わりのメソッド
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すべてから受け取れる。

```cs
// 重い処理の代わりのメソッド(文字列を戻り値で返す)
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プロパティを見る。

```cs
// 戻り値ありの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も固まるので注意)↓↓↓↓↓↓**

```cs
// .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をするときは注意

[こちら](https://qiita.com/acple@github/items/8f63aacb13de9954c5da#task%E3%82%92wait%E3%81%97%E3%81%A6%E3%81%AF%E3%81%84%E3%81%91%E3%81%AA%E3%81%84)に書かれている通り、Waitをするときは注意。
Task内でawaitした後、Taskを呼んだ側でそのTaskをWaitしてしまうと、デッドロックする。

どうしてそうなるか？は、[こちら](https://qiita.com/rawr/items/5d49960a4e4d3823722f#%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%EF%BC%94%E3%81%A9%E3%81%AE%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E3%81%A7%E5%87%A6%E7%90%86%E3%81%8C%E5%AE%9F%E8%A1%8C%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE%E3%81%8B)の解説が分かりやすい。
Task.Run()したとき、awaitしたときのスレッドの動きが図解されている。

# 参考
Taskを極めろ！async/await完全攻略
https://qiita.com/acple@github/items/8f63aacb13de9954c5da

C# 今更ですが、await / async
https://qiita.com/rawr/items/5d49960a4e4d3823722f#%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%EF%BC%94%E3%81%A9%E3%81%AE%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E3%81%A7%E5%87%A6%E7%90%86%E3%81%8C%E5%AE%9F%E8%A1%8C%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE%E3%81%8B

++C++; // 未確認飛行 C
https://ufcpp.net/study/csharp/sp_thread.html

# コード
https://github.com/tera1707/WPF-/tree/master/021_TaskSample