【C#】【Task】Taskでマルチスレッドするときの、async/awaitのある時、ない時の処理の実行順の実験

# やりたいこと
以前の記事を書いてみて、Taskの使い方を何となくわかった気になっていたが、人が書いたTaskを使うコードを見て、何をしてるかよくわからなかったので復習したい。とくに、async/awaitをつける、つけないで動きがどう変わるのか、試してみたい。

# 0.マルチスレッドというが、どこの処理がマルチスレッド(別スレッド)で行われているのか？

### コード

各箇所でスレッドIDを表示させて、どこが同じIDでどこが異なるIDなのか見た。

```cs
// ボタン押したときの処理
private void Button_Click_5(object sender, RoutedEventArgs e)
{
    Console.WriteLine("スレッドID(ボタンハンドラ)：" + Thread.CurrentThread.ManagedThreadId);
    var task1 = TaskFunc();
}

private Task TaskFunc()
{
    var t = Task.Run(() =>
    {
        Console.WriteLine("スレッドID(Task.Run内)：" + Thread.CurrentThread.ManagedThreadId);
    });

    Console.WriteLine("スレッドID(TaskFunc内、Task.Run外)：" + Thread.CurrentThread.ManagedThreadId);

    return t;
}
```

### 実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/452a4417-e038-44ab-7f47-5bee227909a1.png)
結果、Task.Run()の中が、別スレッドになっている。
Taskを返すメソッド(ここではTaskFunc())の中は、画面の処理と同じスレッド。(当たり前？)

# async、awaitのあるなしで、実行順番がどのように変わるのか？

以下、1番からいくつかの実験をしているが、上から順番に少しずつコードを変えて(asyncやawaitをつけたり消したりして)実験することにする。

※呼ぶ側：下でいうところのButton_Click_5のこと。画面のボタンのハンドラ。
　呼ばれる側：下でいうところのTaskFuncの中の処理のこと。

### 1.呼ぶ側、呼ばれる側ともasync/awaitなし

```cs
private void Button_Click_5(object sender, RoutedEventArgs e)
{
    Debug.WriteLine($"A");
    var task1 = TaskFunc(1000); // Taskのメソッドを呼ぶ
    Debug.WriteLine($"B");
    Task.WhenAll(task1);        // WhenAll
    Debug.WriteLine($"C");
}

private Task TaskFunc(int millisec)
{
    Debug.WriteLine("D");
    var t = Task.Run(() => {    // Task.Run
        Debug.WriteLine("E");
        Thread.Sleep(millisec); // Task.Run内Sleep
        Debug.WriteLine("F");
    });
    Debug.WriteLine("G");
    return t;
}
```
実行順：**ADGBCEF**
       **ADEGBCF**
※何度もやると、上のように何パターンか実行順ができる。
　最初が**AD**、最後が**F**だけ1000msSleppするので決まっていて、あとは順番が保証されないと思われる。

### 2.呼ばれる側にasyncだけつける

```cs
private void Button_Click_5(object sender, RoutedEventArgs e)
{
    Debug.WriteLine($"A");
    var task1 = TaskFunc(1000); // Taskのメソッドを呼ぶ
    Debug.WriteLine($"B");
    Task.WhenAll(task1);        // WhenAll
    Debug.WriteLine($"C");
}

private async Task TaskFunc(int millisec)// ★★変化点
{
    Debug.WriteLine("D");
    var t = Task.Run(() => {    // Task.Run
        Debug.WriteLine("E");
        Thread.Sleep(millisec); // Task.Run内Sleep
        Debug.WriteLine("F");
    });
    Debug.WriteLine("G");
}
```

コンパイルは通るが、ワーニングあり。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/434dc58d-79b5-7e91-a61c-da5af2993503.png)

実行順：**ADGBECF**
　　　　**ADEGBCF**
　　　　**ADGBCEF**　
※何度もやると、上のように何パターンか実行順ができる。
　最初が**AD**、最後が**F**だけ1000msSleppするので決まっていて、あとは順番が保証されないと思われる。


### 3.呼ばれる側にasync/awaitつける

```cs
private void Button_Click_5(object sender, RoutedEventArgs e)
{
    Debug.WriteLine($"A");
    var task1 = TaskFunc(1000); // Taskのメソッドを呼ぶ
    Debug.WriteLine($"B");
    Task.WhenAll(task1);        // WhenAll
    Debug.WriteLine($"C");
}

private async Task TaskFunc(int millisec)
{
    Debug.WriteLine("D");
    var t = await Task.Run(() => {    // Task.Run // ★★変化点
        Debug.WriteLine("E");
        Thread.Sleep(millisec); // Task.Run内Sleep
        Debug.WriteLine("F");
    });
    Debug.WriteLine("G");
}
```
実行順：コンパイルエラー
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/383ac035-b25a-9e58-cedf-6208f1f5b9e3.png)

### 4.呼ばれる側にasync/awaitつける(戻り値の型を指定してコンパイル通るようにする)

```cs
private void Button_Click_5(object sender, RoutedEventArgs e)
{
    Debug.WriteLine($"A");
    var task1 = TaskFunc(1000); // Taskのメソッドを呼ぶ
    Debug.WriteLine($"B");
    Task.WhenAll(task1);        // WhenAll
    Debug.WriteLine($"C");
}

private async Task<int> TaskFunc(int millisec)// ★★変化点
{
    Debug.WriteLine("D");
    var t = await Task.Run(() => {  // Task.Run
        Debug.WriteLine("E");
        Thread.Sleep(millisec);     // Task.Run内Sleep
        Debug.WriteLine("F");
        return 1;                           // ★★変化点
    });
    Debug.WriteLine("G");
    return t;                               // ★★変化点
}
```
実行順：**ADBECFG**
　　　　**ADBCEFG**
　　　　**ADEBCFG**
※何度もやると、上のように何パターンか実行順ができる。
　最初が**AD**、最後が**FG**だけ決まっていて、あとは順番が保証されないと思われる。
　最後が**FG**になるのは、Task.Runの完了に1秒かかり、その完了をawaitしているから。

### 5.WhenAllにawaitつけるのを忘れていたのでつける(awaitしないとWhenAllする意味がない)

```cs
private async void Button_Click_5(object sender, RoutedEventArgs e)// ★★変化点
{
    Debug.WriteLine($"A");
    var task1 = TaskFunc(1000); // Taskのメソッドを呼ぶ
    Debug.WriteLine($"B");
    await Task.WhenAll(task1);        // WhenAll                  // ★★変化点
    Debug.WriteLine($"C");
}

private async Task<int> TaskFunc(int millisec)
{
    Debug.WriteLine("D");
    var t = await Task.Run(() => {  // Task.Run
        Debug.WriteLine("E");
        Thread.Sleep(millisec);     // Task.Run内Sleep
        Debug.WriteLine("F");
        return 1;                           
    });
    Debug.WriteLine("G");
    return t;
}
```
実行順：**ADBEFGC**
　　　　**ADEBFGC**
※何度もやると、上のように2パターン実行順ができる。

### 6.呼ぶ側でawaitをつける

```cs
private async void Button_Click_5(object sender, RoutedEventArgs e)
{
    Debug.WriteLine($"A");
    var task1 = await TaskFunc(1000); // Taskのメソッドを呼ぶ // ★★変化点
    Debug.WriteLine($"B");
    await Task.WhenAll(task1);        // WhenAll
    Debug.WriteLine($"C");
}

private async Task<int> TaskFunc(int millisec)
{
    Debug.WriteLine("D");
    var t = await Task.Run(() => {  // Task.Run
        Debug.WriteLine("E");
        Thread.Sleep(millisec);     // Task.Run内Sleep
        Debug.WriteLine("F");
        return 1;           
    });
    Debug.WriteLine("G");
    return t;
}
```
実行順：コンパイルエラー
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/e52bab46-63bc-26b7-d38b-246524805ab3.png)

### 7.メソッドをawaitするので、WhenAllする意味がないので削除する

```cs
private async void Button_Click_5(object sender, RoutedEventArgs e)
{
    Debug.WriteLine($"A");
    var task1 = await TaskFunc(1000); // Taskのメソッドを呼ぶ 
    Debug.WriteLine($"B");
    //await Task.WhenAll(task1);        // WhenAll  // ★★変化点
    Debug.WriteLine($"C");
}

private async Task<int> TaskFunc(int millisec)
{
    Debug.WriteLine("D");
    var t = await Task.Run(() => {  // Task.Run
        Debug.WriteLine("E");
        Thread.Sleep(millisec);     // Task.Run内Sleep
        Debug.WriteLine("F");
        return 1;           
    });
    Debug.WriteLine("G");
    return t;
}
```
実行順：**ADEFGBC**
　　　　この1パターン。

**このパターンは、全部の処理を上から順に実施するので、Taskに分けている意味がないと思われる。**

### 8.ここから、呼ばれる側のasync/awaitを削除

```cs
private async void Button_Click_5(object sender, RoutedEventArgs e)
{
    Debug.WriteLine($"A");
    var task1 = await TaskFunc(1000); // Taskのメソッドを呼ぶ 
    Debug.WriteLine($"B");
    //await Task.WhenAll(task1);        // WhenAll
    Debug.WriteLine($"C");
}

private Task<int> TaskFunc(int millisec)    // ★★変化点
{
    Debug.WriteLine("D");
    var t = Task.Run(() => {  // Task.Run   // ★★変化点
        Debug.WriteLine("E");
        Thread.Sleep(millisec);     // Task.Run内Sleep
        Debug.WriteLine("F");
        return 1;           
    });
    Debug.WriteLine("G");
    return t;
}
```
実行順：**ADGEFBC**
　　　　**ADEGFBC**
　　　　実際には起きなかったが、**ADEFGBC**もあり得るとおもう。

# やってみてわかったこと

- マルチスレッドだから当たり前ではあるが、パラで走っている処理は、実行の順番は保証できない(前後する)。
- Task.Run(...)の実行で、別スレッドが立ち上がってTask.Run()の中に書いた処理が走り出す。

# 再確認した当たり前のこと

- 「Task」にawaitをつけることができる。(当然どこにでも書けるわけではない)
- awaitをつけたTaskは、実行が完了するまで待たれる。(それより下の処理は実行されない)。ここでいうところのTaskは、
    - Task.Run(() => ..)もTask。
    - TaskFunc()が返すのもTask。
    - Task.WhenAll(...)が返すのもTask。
- awaitをつけていないTaskは、完了を待たれない。(上の例8.では、TaskFunc内のTask.Runの次の"G"の処理は、Task.Runとパラで実施される。)
- メインスレッド(UIのスレッド)でTaskをawaitしても、UIは固まらない。
- おそらく、**5.の例**が、**Taskの一般的な使い方**だと思う。(ただし、今回はtask1のみだったが、普通は複数のTaskをRunして、それらをWhenAllで待つと思われる)