C#异步CTP中的“等待”线程会发生什么?
我一直在阅读关于新的async await
关键字,这听起来很棒,但是在迄今为止我看过的任何介绍视频中,我还没有找到答案的关键问题(我也读过一会儿白皮书)。
假设我有一个调用来await
主UI线程上的嵌套函数。 此时线程会发生什么? 控制是否回到消息循环,并且UI线程可以自由处理其他输入?
等待完成的任务完成后,整个堆栈是否会被推送到消息队列中,从而控制将通过每个嵌套函数返回,还是完全发生在这里?
其次(当我引起你的注意的时候),我不明白为什么异步方法需要用async
标记。 不能任何方法异步执行? 如果我想异步执行一个方法但它没有async关键字怎么办?有没有办法简单地做到这一点?
干杯。 :)
编辑:无可否认,如果我能得到示例代码编译,我可能只是自己弄清楚,但由于某种原因,我跑到了那里的一个块。 我真正想知道的是延续在多大程度上持续下去......是否冻结了整个调用堆栈,在任务完成时恢复它,还是只返回到目前为止? 函数本身是否需要标记为异步以支持延续,或者(正如我最初所问)是否继续整个调用堆栈?
如果它不冻结整个调用堆栈,那么当异步等待访问非异步调用函数时会发生什么? 它阻止了吗? 这难道不会等待? 我希望你能看到我在这里错过了一些理解,希望有人可以填写,以便我可以继续学习。
假设我有一个调用来等待主UI线程上的嵌套函数。 此时线程会发生什么? 控制是否回到消息循环,并且UI线程可以自由处理其他输入?
是。 当你await
一个await
时间(比如一个Task<TResult>
)时,线程在async
方法中的当前位置被捕获。 然后在等待完成时(例如,当Task<TResult>
完成时),将方法的剩余部分排队(“继续”)以执行。
但是,可以进行优化:如果awaitable已经完成,那么await
不必等待,并且它会立即继续执行该方法。 这就是所谓的“快速路径”,在这里描述。
等待完成的任务完成后,整个堆栈是否会被推送到消息队列中,从而控制将通过每个嵌套函数返回,还是完全发生在这里?
线程的当前位置被推送到UI消息队列中。 细节稍微复杂一点:在TaskScheduler.FromCurrentSynchronizationContext
上计划延续,除非SynchronizationContext.Current
为null
,在这种情况下,它们将在TaskScheduler.Current
上进行调度。 此外,可以通过调用ConfigureAwait(false)
来覆盖此行为,该总是调度线程池的延续。 由于SynchronizationContext.Current
是WPF / WinForms / Silverlight的UI SynchronizationContext
,因此此延续确实会被推送到UI消息队列中。
其次(当我引起你的注意的时候),我不明白为什么异步方法需要用异步标记。 不能任何方法异步执行? 如果我想异步执行一个方法但它没有async关键字怎么办?有没有办法简单地做到这一点?
这些与“异步”的含义略有不同。 async
关键字启用了await
关键字。 换句话说, async
方法可能会await
。 老式异步委托(即BeginInvoke
/ EndInvoke
)与async
完全不同。 异步委托在ThreadPool
线程上执行,但async
方法在UI线程上执行(假设它们是从UI上下文调用的,并且不调用ConfigureAwait(false)
)。
如果您希望在ThreadPool
线程上运行(非async
)方法,则可以这样做:
await Task.Run(() => MyMethod(..));
我真正想知道的是延续在多大程度上持续下去......是否冻结了整个调用堆栈,在任务完成时恢复它,还是只返回到目前为止? 函数本身是否需要标记为异步以支持延续,或者(正如我最初所问)是否继续整个调用堆栈?
当前位置被捕获,并在继续运行时“恢复”。 任何使用await
支持延续的函数都必须标记为async
。
如果你调用一个async
从非法async
方法,那么你必须处理的Task
直接对象。 这通常不会完成。 顶层的async
方法可能会返回void
,所以没有理由不具有async
事件处理程序。
请注意, async
纯粹是编译器转换。 这意味着async
方法在编译之后就像常规方法一样。 .NET运行时不会以任何特殊方式处理它们。
这取决于Awaitable的行为。
它可以选择运行同步,即它在线程上运行并将控制权返回给同一线程上的等待器。
如果它选择异步运行,awaiter将在线程中回调,等待时间安排回调。 与此同时,调用线程被释放,因为等待启动它是异步工作并退出,并且服务器已连接到等待回调。
至于你的第二个问题,async关键字不是关于该方法是否被异步调用,而是该方法的主体是否想要调用异步代码本身。
即任何返回任务或任务的方法可以异步调用(等待或继续),但也可以通过async标记,该方法现在可以在其主体中使用await关键字,并在返回时不返回任务,但简单地T,因为整个主体将被重写入任务执行的状态机。
假设你有方法
public async Task DoSomething() {
}
当你从UI线程调用它时,你会返回一个任务。 在这一点上,你可以阻止与任务.Wait()
或.Result
它运行的异步方法它的TaskScheduler或阻止.RunSynchronously()
这将在UI线程上运行它。 当然,在DoSomething
内部发生的任何等待基本上是另一个延续,因此它最终可能会在TaskScheduler线程上运行部分代码。 但最终UI线程被阻塞直到完成,就像常规的同步方法一样。
或者您可以使用.ContinueWith()
安排延续,这将创建一个在任务完成时由TaskScheduler调用的操作。 这将立即将控制返回到当前代码,该代码继续执行UI线程中正在执行的任何操作。 continuation不捕获callstack,它只是一个Action,所以它只是捕获它从外部范围访问的任何变量。