呼び出し元がUIスレッドかどうかでawaitした後の戻り先スレッドがどう変わるか実験

tera1707.com

やりたいこと

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周りでいろいろ試した記事

qiita.com

qiita.com

qiita.com