什么线程在`await`关键字后运行代码?

时间:2013-10-23 13:37:23

标签: c# multithreading asynchronous async-await

让我发一个简单的例子:

    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

我不确定这应该如何工作?

2 个答案:

答案 0 :(得分:12)

如果从主线程调用,则在Winform App中发布的代码将“死锁”,因为您使用Wait()阻止了主线程。

  

但在控制台应用程序中这是有效的。但是如何?

答案隐藏在SynchronizationContext.Current中。 await捕获“SynchronizationContext”,当任务完成时,它将继续使用相同的“SynchronizationContext”。

在winform中,应用SynchronizationContext.Current将设置为WindowsFormsSynchronizationContext,它将发布到“消息循环”的调用,但是谁将要处理该消息? out主线程在Wait()等待。

在控制台应用程序中SynchronizationContext.Current默认情况下不会设置,因此当没有“SynchronizationContext”可用于等待捕获时它将为null,因此它会将继续安排到ThreadPool(TaskScheduler) .Default是ThreadpoolTask​​Scheduler)所以等待后的代码工作(通过线程池线程)。

可以使用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开始,然后从那里开始。