[C#] .ConfigureAwait(false)のありなしで、使うスレッド/戻り先スレッドがどう変わるか実験

**もくじ**
→https://qiita.com/tera1707/items/4fda73d86eded283ec4f

# やりたいこと
ある自作ライブラリのメソッド中で、Taskをawaitをしているところがあったので、当然そのメソッドを.Wait()したらデッドロックするのだろうな、と思っていたら、いざやってみるとデッドロックしなかった。

「どんなTaskを.Wait()したらデッドロックするのか」を、MS公式の文書から見つけられなかったので、**なんでデッドロックしなかったのか？またデッドロック回避のための方法は？**をいろいろ試した＆ほかの方の記事を見て調べた結果のメモ。

**【お願い】**
Taskに関する
「こういう書き方をするとデッドロックしますよ」
「こういう書き方をすると、デッドロック回避できますよ」の
Microft公式ドキュメントのありかをご存知の方、ぜひ教えてください。

# 参考

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

どのスレッドで処理が実行されているのか？
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

async/awaitと同時実行制御
https://ufcpp.wordpress.com/2012/11/12/asyncawait%E3%81%A8%E5%90%8C%E6%99%82%E5%AE%9F%E8%A1%8C%E5%88%B6%E5%BE%A1/

CA2007: Do not directly await a Task
https://docs.microsoft.com/ja-jp/visualstudio/code-quality/ca2007?view=vs-2019

# ためしたこと

1. Taskの中でawaitしているメソッドがあると、そのメソッドの処理が終わるまで、呼び出し元のスレッドに戻って処理を継続しようとする
- そのTask(名前をtaskとする)を呼ぶときに、task.Wait()とすると、呼んだスレッドを一旦停止して、taskの処理をしようとする
- で、呼んだスレッドは一旦停止しているのに、taskの中のawaitはそのスレッドに戻ろうとするので、デッドロックする
- デッドロックするパターンでも、awaitするときに**.configureAwait(false)**をつけてやると、戻り先のスレッドをええようにしてくれる(スレッドプールの空いているところに勝手に割り当ててくれる)

という、ざっくり知識はあったので、たぶんこの**戻ろうとするスレッドが動いてるかどうか**、がポイントなんだろうな、と当たりをつけて、そのあたりを調べるために下記のコードを書いて実験。

# 前提

- .netFramework 4.7.2

# 実験１ .ConfigureAwait(false)をつけているawaitがあるタスクを、UIスレッドから.Wait()で呼ぶ

- Taskを呼び出すのは、UIスレッド
- Taskの中のawaitするところに、.ConfigureAwait(false) をつける
- Taskを呼ぶときに.Wait()する

```cs
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click start");
            {
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just before");
                func1().Wait();
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just after");
            }
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click end");
        }
        private async Task func1()
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.0");
            await Task.Delay(2000).ConfigureAwait(false);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.1");
            await Task.Delay(2000).ConfigureAwait(false);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.2");
            await Task.Delay(2000).ConfigureAwait(false);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.3");
        }
```

```出力結果.txt
00:58:28 1: Button_Click start
00:58:28 1: func1 just before
00:58:28 1: func1 No.0
00:58:30 3: func1 No.1
00:58:32 3: func1 No.2
00:58:34 3: func1 No.3
00:58:34 1: func1 just after
00:58:34 1: Button_Click end
```

UIスレッドは、スレッドNo.1。
Taskの中も、await Task.Delayの前まではUIスレッドと同じスレッドNo.1でうごいている。
一度、await Task.Delayを行うと、そのあとからTaskを抜けるまで、スレッドNo.が変わる。(No.3)
Taskを抜けると、もとのUIスレッドNo.に戻ってくる。
**（ただし、taskが処理をしている間、UIはフリーズした状態になっている！）**
→ちゃんとしたアプリでは使えなさそう。


# 実験２ .ConfigureAwait(false)をつけていないawaitがあるタスクを、UIスレッドから.Wait()で呼ぶ

- Taskを呼び出すのは、UIスレッド
- Taskの中のawaitするところに、.ConfigureAwait(false) をつけ**ない**
- Taskを呼ぶときに.Wait()する

```cs
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click start");
            {
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just before");
                func1().Wait();
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just after");
            }
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click end");
        }
        private async Task func1()
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.0");
            await Task.Delay(2000);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.1");
            await Task.Delay(2000);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.2");
            await Task.Delay(2000);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.3");
        }
```

```出力結果.txt
01:02:29 1: Button_Click start
01:02:29 1: func1 just before
01:02:29 1: func1 No.0
(ここでデッドロック)
```

Taskの中の、最初のawaitを行った時点で、デッドロックする。
awaitでUIスレッドに戻ろうとしたが、**func1().Wait();**でUIスレッドが待ちに入っているので戻ることができず、デッドロックしたと思われる。


# 実験３ .ConfigureAwait(false)をつけていないawaitがあるタスクを、別スレッドから.Wait()で呼ぶ

- Taskを呼び出すのは、UIスレッド**ではない別のスレッド**
- Taskの中のawaitするところに、.ConfigureAwait(false) をつけ**ない**
- Taskを呼ぶときに.Wait()する

```cs
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click start");
            var t = Task.Run(() =>
            {
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just before");
                func1().Wait();
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just after");
            });
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click end");
        }
        private async Task func1()
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.0");
            await Task.Delay(2000);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.1");
            await Task.Delay(2000);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.2");
            await Task.Delay(2000);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.3");
        }
    }
```

```出力結果.txt
01:08:15 1: Button_Click start
01:08:15 1: Button_Click end
01:08:15 3: func1 just before
01:08:15 3: func1 No.0
01:08:17 4: func1 No.1
01:08:19 4: func1 No.2
01:08:21 4: func1 No.3
01:08:21 3: func1 just after
```

- UIスレッドからtaskを呼んだ時にはデッドロックしていたのが、UIスレッドではない別のスレッドから同じ呼び方をすると、デッドロックしなくて済んだ。
- UIスレッドから呼んだtaskの中のawaitは、処理が終わると**UIスレッドに戻ろうとする**が、別のスレッドから呼んだtaskの中のawaitは、処理が終わると**空いているスレッドを使おうとする**ように見える。
- 結果、.Wait()で止めているスレッドに戻ろうとしないので、デッドロックしなくて済んでいる。

※たぶん、冒頭で書いていた、中でawaitしてるライブラリのメソッドなのに、そいつを.Wait()してもデッドロックしない奴、は、このパターンだったのでは？と思う。(まだ未確認)

# 実験４ .ConfigureAwait(false)をつけているawaitがあるタスクを、UIスレッドからawaitで呼ぶ

- Taskを呼び出すのは、UIスレッド
- Taskの中のawaitするところに、.ConfigureAwait(false) をつけ**る**
- Taskを呼ぶときにも**awaitする**

```cs
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click start");
            {
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just before");
                await func1();
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just after");
            }
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click end");
        }
        private async Task func1()
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.0");
            await Task.Delay(2000).ConfigureAwait(false);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.1");
            await Task.Delay(2000).ConfigureAwait(false);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.2");
            await Task.Delay(2000).ConfigureAwait(false);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.3");
        }
```

```出力結果.txt
01:19:22 1: Button_Click start
01:19:22 1: func1 just before
01:19:22 1: func1 No.0
01:19:24 3: func1 No.1
01:19:26 3: func1 No.2
01:19:28 3: func1 No.3
01:19:28 1: func1 just after
01:19:28 1: Button_Click end
```

taskの中で、一回awaitした後のスレッドNo.が、UIとは別のスレッドになる。

# 実験５ .ConfigureAwait(false)をつけていないawaitがあるタスクを、UIスレッドからawaitで呼ぶ

- Taskを呼び出すのは、UIスレッド
- Taskの中のawaitするところに、.ConfigureAwait(false) をつけ**ない**
- Taskを呼ぶときにも**awaitする**

```cs
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click start");
            {
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just before");
                await func1();
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just after");
            }
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click end");
        }
        private async Task func1()
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.0");
            await Task.Delay(2000);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.1");
            await Task.Delay(2000);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.2");
            await Task.Delay(2000);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.3");
        }
```

```出力結果.txt
01:21:27 1: Button_Click start
01:21:27 1: func1 just before
01:21:27 1: func1 No.0
01:21:29 1: func1 No.1  // ←スレッドNo.が、実験4とことなる！！！
01:21:31 1: func1 No.2
01:21:33 1: func1 No.3
01:21:33 1: func1 just after
01:21:33 1: Button_Click end
```

実験４と、ログが出力されるタイミングはまったく同じだが、
taskの中で一回awaitした後の戻り先のスレッドNo.が異なる。
**つまり、UIスレッドから呼ばれてawaitした処理はUIスレッドに戻ろうとする、ということだと思われる。**

# 実験６ .ConfigureAwait(false)をつけていないawaitがあるタスクを、別スレッドからawaitで呼ぶ

- Taskを呼び出すのは、UIスレッド**ではない別のスレッド**
- Taskの中のawaitするところに、.ConfigureAwait(false) をつけ**ない**
- Taskを呼ぶときにも**awaitする**

```cs
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click start");
            var t = Task.Run(async () =>
            {
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just before");
                await func1();
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just after");
            });
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click end");
        }
        private async Task func1()
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.0");
            await Task.Delay(2000);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.1");
            await Task.Delay(2000);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.2");
            await Task.Delay(2000);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.3");
        }
```

```出力結果.txt
01:28:12 1: Button_Click start
01:28:12 1: Button_Click end
01:28:12 3: func1 just before
01:28:12 3: func1 No.0
01:28:14 3: func1 No.1
01:28:16 3: func1 No.2
01:28:18 3: func1 No.3
01:28:18 3: func1 just after
```

# 実験７ .ConfigureAwait(false)をつけているawaitがあるタスクを、別スレッドからawaitで呼ぶ

- Taskを呼び出すのは、UIスレッド**ではない別のスレッド**
- Taskの中のawaitするところに、.ConfigureAwait(false) をつけ**る**
- Taskを呼ぶときにも**awaitする**

```cs
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click start");
            var t = Task.Run(async () =>
            {
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just before");
                await func1();
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 just after");
            });
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: Button_Click end");
        }
        private async Task func1()
        {
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.0");
            await Task.Delay(2000).ConfigureAwait(false);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.1");
            await Task.Delay(2000).ConfigureAwait(false);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.2");
            await Task.Delay(2000).ConfigureAwait(false);
            Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} {Thread.CurrentThread.ManagedThreadId}: func1 No.3");
        }
```

```出力結果.txt
01:30:41 1: Button_Click start
01:30:41 1: Button_Click end
01:30:41 4: func1 just before
01:30:41 4: func1 No.0
01:30:43 3: func1 No.1
01:30:45 3: func1 No.2
01:30:47 3: func1 No.3
01:30:47 3: func1 just after
```



# 追加でかきたいこと

・実験２のawaitするTaskの中に、awaitする処理が一つもないと、デッドロックしない
　→最初に挙げた動きの原因は、間が抜けているが、これだった。

・async/awaitが階層になっていて、一番上で.Wait()する場合、
　間に挟まるawaitするタスクは、一つでも.ConfigureAwait(false)が抜けていると、デッドロックする。