当我编写以下程序时,我正在弄乱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窗体应用程序中,它总是由主线程恢复。
这是为什么?它可靠吗?
答案 0 :(得分:2)
是。它很可靠。
执行await
时,会捕获当前SynchronizationContext,以便在await
之后继续执行此上下文时,将使用此上下文执行代码。
在GUI应用程序中,当前SynchronizationContext
是一个将在UI线程上执行代码的上下文。
在控制台应用程序中,当前SynchronizationContext
为null
,因此任务在线程池线程上继续。
答案 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所示。