なぜTask.Run()がUIスレッド/オリジンコンテキストに同期するのを待たないのですか?


8

非同期待ちのパターンTask.Run動作は理解できたと思いました。
しかし、次のコード例でawait、完了したタスクから戻った後、なぜUIスレッドに同期しないのかと思います。

public async Task InitializeAsync()
{
    Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // "Thread: 1"
    double value = await Task.Run(() =>
    {
        Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // Thread: 6

        // Do some CPU expensive stuff
        double x = 42;
        for (int i = 0; i < 100000000; i++)
        {
            x += i - Math.PI;
        }
        return x;
    }).ConfigureAwait(true);
    Console.WriteLine($"Result: {value}");
    Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // Thread: 6  - WHY??
}

このコードは、Visual Studio 2019デバッガーが接続されたWindows 10システム上の.NET Framework WPFアプリケーション内で実行されます。
私のAppクラスのコンストラクターからこのコードを呼び出しています。

public App()
{
    this.InitializeAsync().ConfigureAwait(true);
}

多分それが最善の方法ではないかもしれませんが、これが奇妙な行動の理由であるかどうかはわかりません。

コードはUIスレッドから始まり、いくつかのタスクを実行する必要があります。await操作し、ConfigureAwait(true)それがメインスレッド(1)に続けるべきタスクが終了した後。しかし、そうではありません。

どうして?


4
@SushantYelpaleが正しくない
MickyD

回答:


10

それはトリッキーなことです。

あなたはawaitUIスレッドを呼び出しています、それは本当です。だが!あなたはAppのコンストラクタの中でそれをやっています。

暗黙的に生成されたスタートアップコードは次のようになります。

public static void Main()
{
    var app = new YourNamespace.App();
    app.InitializeComponent();
    app.Run();
}

メインスレッドに戻るために使用されるイベントループは、Run実行の一部としてのみ開始されます。そのため、Appコンストラクターの実行中、イベントループはありません。まだ。

結果として、のSynchronizationContextawaitにメインスレッドへのフローの戻りを技術的に担当するはnull、アプリのコンストラクターにあります。

(待機SynchronizationContextするawait 前にがキャプチャされるため、終了後にTask有効なものがすでに存在することは問題ではありませんSynchronizationContext。キャプチャされた値はnullなのでawait、スレッドプールスレッドで実行を継続します。)

したがって、問題は、コンストラクターでコードを実行していることではなく、のコンストラクターでコードを実行していることです。Appこの時点では、アプリケーションはまだ実行用に完全にセットアップされていません。MainWindowのコンストラクタ内の同じコードはうまく動作します。

いくつかの実験をしてみましょう:

public App()
{
    Console.WriteLine($"sc = {SynchronizationContext.Current?.ToString() ?? "null"}");
}

protected override void OnStartup(StartupEventArgs e)
{
    Console.WriteLine($"sc = {SynchronizationContext.Current?.ToString() ?? "null"}");
    base.OnStartup(e);
}

最初の出力は

sc = null

二番目

sc = System.Windows.Threading.DispatcherSynchronizationContext

これで、すでにOnStartup同期コンテキストがあることがわかります。したがって、に移動InitializeAsync()するとOnStartup、期待どおりに動作します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.