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

und*_*ind 1 c# wpf asynchronous task async-await

假设我有一个简单的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

当5000毫秒通过时,我们看到"继续在子线程上运行"消息.当另一个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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

任何帮助将不胜感激.

i3a*_*non 9

async-await尊重当前的范围SynchronizationContext.这意味着在异步操作开始时捕获上下文(如果存在),并且当它结束时,将在捕获的上下文上调度continuation.

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

ASP.Net也有它自己SynchronizationContextAspNetSynchronizationContext(令人惊讶).所以它不一定是关于UI单线程公寓.


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

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

有关SynchronizationContexts的更多信息:它是关于SynchronizationContext的全部内容