让我发一个简单的例子:
private void MyMethod()
{
Task task = MyAsyncMethod();
task.Wait();
}
private async Task MyAsyncMethod()
{
//Code before await
await MyOtherAsyncMethod();
//Code after await
}
假设我在单线程应用程序中运行上述代码,就像控制台应用程序一样。我很难理解代码//Code after await
将如何运行。
我了解当我点击await
中的MyAsyncMethod()
关键字后,控件会返回MyMethod()
,但我会用task.Wait()
锁定该主题。如果线程被锁定,那么如果应该接受它的线程被锁定,//Code after await
如何运行?
是否创建了一个新线程来运行//Code after await
?或者主线程是否神奇地从task.Wait()
跳出来运行//Code after await
?
我不确定这应该如何工作?
答案 0 :(得分:12)
如果从主线程调用,则在Winform App中发布的代码将“死锁”,因为您使用Wait()
阻止了主线程。
但在控制台应用程序中这是有效的。但是如何?
答案隐藏在SynchronizationContext.Current
中。 await
捕获“SynchronizationContext”,当任务完成时,它将继续使用相同的“SynchronizationContext”。
在winform中,应用SynchronizationContext.Current
将设置为WindowsFormsSynchronizationContext
,它将发布到“消息循环”的调用,但是谁将要处理该消息? out主线程在Wait()
等待。
在控制台应用程序中SynchronizationContext.Current
默认情况下不会设置,因此当没有“SynchronizationContext”可用于等待捕获时它将为null
,因此它会将继续安排到ThreadPool
(TaskScheduler) .Default是ThreadpoolTaskScheduler)所以等待后的代码工作(通过线程池线程)。
可以使用Task.ConfigureAwait(false);
来控制上述捕获行为,这将阻止winform应用程序死锁,但await
之后的代码不再在UI线程中运行。
答案 1 :(得分:11)
是否创建了一个新线程来运行//等待后的代码?
也许。也许不吧。 Task
的等待模式实现使用在await表达式开始时为“current”的同步上下文运行continuation(await
表达式之后的位)。例如,如果您处于UI线程的上下文中,则意味着您将最终返回到相同的UI线程。如果你在一个线程池线程中,你将最终回到一些线程池线程,但它可能是另一个。
当然,使用您的代码示例,如果您在UI线程中,您对Wait()
的调用将阻止UI线程,以便继续无法运行 - 您需要要小心。 (对您不知道要完成的任务调用Wait()
或Result
,以及哪些可能需要在当前线程上工作,这是一个坏主意。)
请注意,您可以致电Task.ConfigureAwait
,以便表达不要求继续使用相同上下文的意图。这通常适用于不关心它们运行在哪个线程上的库方法:
await task.ConfigureAwait(false);
(它影响的不仅仅是线程 - 它是捕获与否的整个上下文。)
我认为通过等待来熟悉引擎盖下的内容是个好主意。网上有大量的文档,如果你允许我简短的插件,还有3rd edition of C# in Depth和我的Tekpub screencast series on the topic。或者从MSDN开始,然后从那里开始。