在C#Async CTP中`等待'线程会发生什么?

时间:2011-08-11 20:19:37

标签: c# .net async-ctp async-await

我一直在阅读有关新的异步await关键字,这听起来很棒,但有一个关键问题我无法在我看过的任何介绍视频中找到答案到目前为止(我还读过一段时间的白皮书)。

假设我在主UI线程的嵌套函数中调用了await。此时线程会发生什么?控制是否返回消息循环,UI线程可以自由处理其他输入?

当等待的任务完成时,整个堆栈是否会被推送到一个消息队列,这样控件将通过每个嵌套函数返回,或者是否完全发生在这里呢?

其次(虽然我引起了你的注意),我真的不明白为什么异步方法需要用async标记。不能异步执行任何方法吗?如果我想异步执行一个方法但它没有async关键字怎么办?有没有办法简单地做到这一点?

干杯。 :)

修改 无可否认,如果我能够获得示例代码编译,我可能只是想出自己,但出于某种原因,我遇到了一个块。我真正想知道的是,延续的持续程度是多久...是否会冻结整个调用堆栈,在任务完成时恢复它,还是只返回到目前为止?函数本身是否需要标记为异步以支持延续,或者(正如我最初所说的那样)它是否继续整个调用堆栈?

如果没有冻结整个调用堆栈,那么当异步等待命中非异步调用函数时会发生什么?它阻止了吗?这不会打败等待点吗?我希望你能看到我在这里缺少一些理解,希望有人可以填写,这样我就可以继续学习。

2 个答案:

答案 0 :(得分:26)

  

假设我在主UI线程上的嵌套函数中调用了await。此时线程会发生什么?控制是否返回消息循环,UI线程可以自由处理其他输入?

是。当您await等待(例如Task<TResult>)时,会捕获线程在async方法中的当前位置。然后它将等待方法的剩余部分(“继续”)在等待结束时执行(例如,当Task<TResult>完成时)。

但是,可以进行优化:如果等待已经完成,那么await不必等待,它只是立即继续执行该方法。这被称为“快速路径”,described here

  

当等待的任务完成时,整个堆栈是否会被推送到一个消息队列,这样控件将通过每个嵌套函数返回,或者是否完全发生在这里呢?

线程的当前位置被推送到UI消息队列。详细信息稍微复杂一些:继续安排在TaskScheduler.FromCurrentSynchronizationContext上,除非SynchronizationContext.Currentnull,在这种情况下,它们会安排在TaskScheduler.Current上。此外,可以通过调用ConfigureAwait(false)来覆盖此行为,SynchronizationContext.Current始终调度线程池上的延续。由于SynchronizationContext是WPF / WinForms / Silverlight的UI async,因此此延续会被推送到UI消息队列。

  

其次(虽然我引起了你的注意),我真的不明白为什么异步方法需要用async标记。不能异步执行任何方法吗?如果我想异步执行一个方法但它没有async关键字怎么办?有没有办法简单地做到这一点?

“异步”一词的含义略有不同。 await关键字启用async关键字。换句话说,await方法可能BeginInvoke。老式异步委托(即EndInvoke / async)与ThreadPool完全不同。异步委托在async线程上执行,但ConfigureAwait(false)方法在UI线程上执行(假设它们是从UI上下文调用的,并且您不调用async)。

如果你想在ThreadPool线程上运行(非await Task.Run(() => MyMethod(..)); )方法,你可以这样做:

await
  

我真正想知道的是延续在多大程度上继续...它是否会冻结整个调用堆栈,在任务完成时恢复它,还是只返回到目前为止?函数本身是否需要标记为异步以支持延续,或者(正如我最初所说的那样)它是否继续整个调用堆栈?

捕获当前位置,并在继续运行时“恢复”。使用async支持延续的任何功能都必须标记为async

如果您使用非async方法调用Task方法,则必须直接处理async对象。通常不会这样做。顶级void方法可能会返回async,因此没有理由不让async个事件处理程序。

请注意async纯粹是编译器转换。这意味着{{1}}方法在编译后与常规方法完全相同。 .NET运行时不会以任何特殊方式处理它们。

答案 1 :(得分:4)

这取决于Awaitable的行为。

它可以选择运行同步,即它在线程上运行并将控制权返回给同一线程上的awaiter。

如果它选择异步运行,则awaiter将在等待调度回调的线程上回调。在此期间,调用线程被释放,因为等待启动它的异步工作并退出,并且等待者已经将其继续附加到等待的回调。

关于你的第二个问题,async关键字不是关于该方法是否异步调用,而是该方法的主体是否想要内联调用异步代码。

即。任何返回任务或任务的方法都可以异步调用(等待或使用continue),但也可以用异步标记它,该方法现在可以在其体内使用await关键字,当它返回时它不返回任务,而只是T,因为整个主体将被重写为任务执行的状态机。

假设你有方法

public async Task DoSomething() {
}

当你从UI线程调用它时,你会得到一个任务。此时,您可以使用.Wait().Result来阻止任务,该任务在其TaskScheduler上运行async方法,或者使用.RunSynchronously()阻止它在UI线程上运行它。当然,DoSomething内发生的任何等待都是另一个延续,因此最终可能会在TaskScheduler线程上运行部分代码。但最终UI线程被阻塞直到完成,就像常规的同步方法一样。

或者您可以使用.ContinueWith()安排延续,这将创建一个在任务完成时由TaskScheduler调用的操作。这将立即将控制权返回给当前代码,该代码继续执行它在UI线程上执行的任何操作。延续不捕获callstack,它只是一个Action,所以它只捕获它从外部范围访问的任何变量。