首页 > 解决方案 > 为什么不等待 Task.Run() 同步回 UI 线程/原始上下文?

问题描述

我以为我了解异步等待模式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) 上继续。但事实并非如此。

为什么?

标签: c#wpfmultithreadingasync-awaittask

解决方案


这是一件棘手的事情。

您正在调用awaitUI 线程,这是真的。但!您正在App的构造函数中执行此操作。

请记住,隐式生成的启动代码如下所示:

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

用于返回主线程的事件循环仅作为Run执行的一部分启动。所以在App构造函数运行期间,没有事件循环。然而。

因此,在SynchronizationContext技术上负责在 之后将流返回到主线程await的 ,null位于 App 的构造函数中。

(在等待之前SynchronizationContext被捕获,所以完成之后已经有一个有效的没有关系:捕获的值是,所以继续在线程池线程上执行。)await TaskSynchronizationContextnullawait

所以问题不在于您在构造函数中运行代码,问题在于您在 的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,它将按照您的预期运行。


推荐阅读