在等待异步方法后更改ui属性是否安全?

时间:2015-12-05 14:54:46

标签: c# asynchronous async-await

当我编写以下程序时,我正在弄乱c#的await / async:

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        Test();

        while (true) { }
    }

    static async void Test()
    {
        var t = Task.Run(()=> Thread.Sleep(1000));

        await t;

        throw new Exception("This exception happens in a worker thread");
    }
}

当我运行这个程序时,我可以在Visual Studio的线程窗口中清楚地看到异常发生在Worker Thread而不是Main Thread,这让我相信当我等待任务时,我的方法可以由另一个线程完成。

但是,当我打开一个新的Windows窗体应用程序并添加以下事件处理程序时:

    async void button1_Click(object sender, EventArgs e)
    {
        var task = Task.Run(() => Thread.Sleep(1000));

        await task;

        button1.Text = "I'm in the main thread!"; //this works
    }

这是有效的,所以我注意到在控制台应用程序中,我的Test函数由工作线程恢复,而在Windows窗体应用程序中,它总是由主线程恢复。

这是为什么?它可靠吗?

3 个答案:

答案 0 :(得分:2)

是。它很可靠。

执行await时,会捕获当前SynchronizationContext,以便在await之后继续执行此上下文时,将使用此上下文执行代码。

在GUI应用程序中,当前SynchronizationContext是一个将在UI线程上执行代码的上下文。

在控制台应用程序中,当前SynchronizationContextnull,因此任务在线程池线程上继续。

看看this blog post

答案 1 :(得分:2)

正如我在async intro中解释的那样,默认情况下await将捕获“上下文”并在该上下文中继续执行其async方法。

从技术上讲,此上下文为SynchronizationContext.Current ,除非它是null ,在这种情况下它是TaskScheduler.Current

在日常用语中,这意味着如果方法在UI线程上运行,则上下文是UI上下文;如果方法正在为ASP.NET请求提供服务,那么它是一个ASP.NET请求上下文;并且它很可能是所有其他情况下的线程池上下文。

请注意,这是默认行为。您可以通过等待ConfigureAwait(continueOnCapturedContext: false)的结果来指定方法不需要在其上下文中恢复。在这种情况下,很可能该方法的其余部分将在线程池线程上执行;但从技术上讲,这只是意味着该方法将在“某处”执行,而您并不关心在哪里。

答案 2 :(得分:1)

是的,这是正确的,也是async / await的预期用途之一。 您正在等待的Task是异步发生的。当它恢复时,它将在调用线程上恢复,在WinForms事件处理程序的情况下,它将是UI线程。

请注意,您可以使用Task.ConfigureAwait(False)更改此行为。当以这种方式配置任务时,它不会将控制权传递回原始线程,而是在任务的线程上恢复。

所有这些行为都取决于应用程序的当前SynchonizationContext。某些应用程序类型将具有不同的上下文,可以巧妙地更改行为。控制台应用程序就是一个很好的例子,因为它们默认情况下不设置同步上下文,并将在Task的线程上恢复。如果需要,可以通过为控制台应用创建上下文来更改此设置,如here所示。

相关问题