Task'的延续(由async/await构建)在WPF应用程序的主线程上运行,但在控制台应用程序的子线程上运行

本文关键字:线程 应用程序 运行 WPF 控制台 构建 延续 Task await async | 更新日期: 2023-09-27 18:07:03

假设我有一个简单的C#控制台应用程序:

class Program
{
    static async void func()
    {
        Thread.CurrentThread.Name = "main";
        await Task.Run(() =>
        {
            Thread.CurrentThread.Name = "child";
            Thread.Sleep(5000);
        });
        Console.WriteLine("continuation is running on {0} thread", Thread.CurrentThread.Name);
    }
    static void Main(string[] args)
    {
        func();
        Thread.Sleep(10000);
    }
}

当5000毫秒过去时,我们看到"continuation正在子线程上运行"消息。当另一个5000毫秒过去时,主线程完成它的工作并关闭应用程序。这看起来很符合逻辑:异步任务和它的延续在同一个子线程上运行。

但假设现在我有一个简单的WPF应用程序:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
    async private void mainWnd_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        Thread.CurrentThread.Name = "main";
        await Task.Run(() =>
        {
            Thread.CurrentThread.Name = "child";
            Thread.Sleep(5000);
        });
        this.Title = string.Format("continuation is running on {0} thread", Thread.CurrentThread.Name);
    }
    private void mainWnd_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
        Thread.Sleep(10000);
    }
}

现在,当我们按下鼠标左键并经过5000毫秒后,我们看到"continuation正在主线程上运行"标题。此外,如果我们按左键,然后按右键,应用程序将首先运行mainWnd_MouseLeftButtonDown处理程序,然后运行mainWnd_MouseRightButtonDown处理程序(在主线程上),主线程将休眠10000毫秒,然后从mainWnd_MouseLeftButtonDown继续异步任务将在主线程上执行。

为什么async-await机制在这两种情况下不同?

我知道在WPF方法可以显式运行在UI线程通过Dispatcher.Invoke,但async-await机制不是WPF特定的,所以它的行为应该是平等的,在任何类型的应用程序,不应该吗?

Task'的延续(由async/await构建)在WPF应用程序的主线程上运行,但在控制台应用程序的子线程上运行

async-await尊重当前作用域的SynchronizationContext。这意味着在异步操作开始时捕获上下文(如果存在),并且在异步操作结束时在捕获的上下文上调度延续。

UI应用程序(WPF/Winforms)使用SynchronizationContext,只允许主(UI)线程与UI元素交互,因此它与async-await无缝工作。

ASP.Net也有自己的SynchronizationContext,称为AspNetSynchronizationContext(令人惊讶)。所以它不一定是关于UI或单线程公寓。


如果你想禁用有用的SynchronizationContext捕获,你只需要使用ConfigureAwait:

await Task.Run(() =>
{
    Thread.CurrentThread.Name = "child";
    Thread.Sleep(5000);
}).ConfigureAwait(false);

更多关于SynchronizationContexts: It's All About the SynchronizationContext