やりたいこと
await HidoukiMethod();
の実行後は、.ConfigureAwait(false)
をしない限りは同じスレッドに戻ってくると思っていたが、なんだかそうではない動きをしていたプログラムがあった。(wait後、別スレッドに戻っている??)
どういうときに、await後に別スレッドに戻るような動作になるのか調べたい。
前提
- C#10
- WinUI3 + .NET6
- コンソール + .NET6
結果
UIスレッドから呼んだawait HidoukiMethod();
は、UIスレッドに戻るが、
UIスレッドではないスレッドから呼んだawait HidoukiMethod();
は、別のスレッドに戻っていた。
以下、それについての実験。
①UIスレッドから非同期メソッドをawaitで呼ぶ
画面
private async void myButton_Click(object sender, RoutedEventArgs e) { Debug.WriteLine($"①:{Thread.CurrentThread.ManagedThreadId}"); await AsyncClass.FuncAsync(); Debug.WriteLine($"②:{Thread.CurrentThread.ManagedThreadId}"); }
呼ぶ処理
public static class AsyncClass { public static async Task FuncAsync() { Debug.WriteLine($"A:{Thread.CurrentThread.ManagedThreadId}"); await Task.Run(() => { Debug.WriteLine($"B:{Thread.CurrentThread.ManagedThreadId}"); Debug.WriteLine($"C:{Thread.CurrentThread.ManagedThreadId}"); } ); Debug.WriteLine($"D:{Thread.CurrentThread.ManagedThreadId}"); } }
結果
①:1 A:1 B:4 C:4 D:1 ②:1
UIスレッド(ID=1)が呼んだTask.Run(処理);
は、awaitの後にUIスレッドに戻ってくる。
②UIスレッドでawait Task.Run()して、その中で非同期メソッドを呼ぶ
①で呼んでるawait AsyncClass.FuncAsync();
を、await Task.Run();で包んで、2段の非同期にする。
※2段目の非同期処理は、呼び出し元がUIスレッドではない。
画面
private async void myButton_Click(object sender, RoutedEventArgs e) { Debug.WriteLine($"①:{Thread.CurrentThread.ManagedThreadId}"); await Task.Run(async () => { await AsyncClass.FuncAsync(); }); Debug.WriteLine($"②:{Thread.CurrentThread.ManagedThreadId}"); }
処理
①と同じ。
結果
①:1 A:4 B:6 C:6 D:6 ②:1
UIスレッド(ID=1)が呼んだawait Task.Run(処理);
は、awaitの後にUIスレッドに戻ってくる。
(①と②はID=1)
が、FuncAsync()
の中のawait Task.Run()の前はID=4だが、await Task.Run()の中はID=6、await Task.Run()終了後もID=6で、await した後、もとのスレッドに戻っていない。
③UIスレッドから非同期メソッドをawaitで呼んで.ConfigureAwait(false)する
①のawait AsyncClass.FuncAsync();
を、await AsyncClass.FuncAsync().ConfigureAwait(false);
にする。
画面
private async void myButton_Click(object sender, RoutedEventArgs e) { Debug.WriteLine($"①:{Thread.CurrentThread.ManagedThreadId}"); await AsyncClass.FuncAsync().ConfigureAwait(false); Debug.WriteLine($"②:{Thread.CurrentThread.ManagedThreadId}"); }
処理
①と同じ
結果
①:1 A:1 B:4 C:4 D:1 ②:6
UIスレッドから呼んだawaitする非同期メソッドは、元々UIスレッドに帰っていたが、.ConfigureAwait(false)
を付けると、別のスレッドに戻るようになる。
(スレッドプールから、動けるスレッドを取ってくるようになる。)
※だから、.ConfigureAwait(false)
を付けると、Asyncメソッドを.Wait()
しても、デッドロックしなくできる。
ざっくりまとめ
- UIスレッドから呼んだawait Task.Run();は、処理終了後、元のUIスレッドに戻る。
- UIスレッドではないスレッドから呼んだawait Task.Run();は、処理終了後、元のUIスレッドには戻らない。
- UIスレッドから呼んだawait Task.Run().ConfigureAwait(false);は、処理終了後、元のUIスレッドには戻らない。
参考
await演算子と同期コンテキスト
async/awaitと同時実行制御ufcpp.wordpress.com
以前、Task周りでいろいろ試した記事