C# 异步 CTP 中的“等待”线程会发生什么情况
本文关键字:线程 什么情况 等待 异步 CTP 中的 | 更新日期: 2023-09-27 18:05:11
我一直在阅读有关新的异步await
关键字的信息,听起来很棒,但是有一个关键问题我无法在迄今为止观看的任何介绍视频中找到答案(不久前我也阅读了白皮书(。
假设我调用了主 UI 线程上的嵌套函数await
。此时线程会发生什么情况?控件是否返回到消息循环,并且 UI 线程是否可以自由处理其他输入?
当等待的任务完成时,整个堆栈是否被推送到消息队列中,以便控制权将通过每个嵌套函数返回,或者这里完全发生了其他事情?
其次(虽然我引起了您的注意(,但我真的不明白为什么异步方法需要用 async
标记。不能异步执行任何方法吗?如果我想异步执行一个方法,但它没有异步关键字怎么办 - 有没有办法简单地做到这一点?
干杯。 :)
编辑:诚然,如果我能得到示例代码编译,我可能只是自己弄清楚,但由于某种原因,我在那里遇到了一个障碍。我真正想知道的是,延续到什么程度......它是冻结整个调用堆栈,以便在任务完成时恢复它,还是只返回到目前为止?函数本身是否需要标记为异步以支持延续,或者(正如我最初问的那样(它是否继续整个调用堆栈?
如果它没有冻结整个调用堆栈,当异步等待命中非异步调用函数时会发生什么?它堵在那里吗?这岂不是打败了等待的意义?我希望你能看到我在这里缺少一些理解,我希望有人可以填写,以便我可以继续学习。
假设我在主 UI 线程上的嵌套函数中调用了 await。此时线程会发生什么情况?控件是否返回到消息循环,并且 UI 线程是否可以自由处理其他输入?
是的。await
可等待(如Task<TResult>
(时,将捕获线程在 async
方法中的当前位置。然后,它将方法的其余部分("延续"(排队,以便在可等待完成时(例如,当Task<TResult>
完成时(执行。
但是,可以进行优化:如果 awaitable 已经完成,则await
不必等待,它只是立即继续执行该方法。这称为"快速路径",如此处所述。
当等待的任务完成时,整个堆栈是否被推送到消息队列中,以便控制权将通过每个嵌套函数返回,或者这里完全发生了其他事情?
线程的当前位置被推送到 UI 消息队列中。细节有点复杂:除非null
SynchronizationContext.Current
,否则延续安排在TaskScheduler.FromCurrentSynchronizationContext
,在这种情况下,它们被安排在TaskScheduler.Current
。此外,可以通过调用 ConfigureAwait(false)
来覆盖此行为,该 始终计划线程池上的延续。由于SynchronizationContext.Current
是 WPF/WinForms/Silverlight 的 UI SynchronizationContext
,因此此延续确实被推送到 UI 消息队列中。
其次(虽然我引起了您的注意(,但我真的不明白为什么异步方法需要用异步标记。不能异步执行任何方法吗?如果我想异步执行一个方法,但它没有异步关键字怎么办 - 有没有办法简单地做到这一点?
这些是"异步"一词略有不同的含义。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 运行时不会以任何特殊方式处理它们。
这取决于 Await 的行为。
它可以选择同步运行,即它在线程上运行并将控制权返回给同一线程上的等待者。
如果它选择异步运行,则等待者将在可等待计划回调的线程上被回调。与此同时,调用线程被释放,因为有 awaitable start 它是异步工作并退出,并且等待者的延续已附加到 awaitable 的回调。
至于你的第二个问题,async 关键字不是关于该方法是否异步调用,而是该方法的主体是否想要内联调用异步代码本身。
即任何返回任务或任务的方法都可以异步调用(等待或继续(,但通过也用异步标记它,该方法现在可以在其正文中使用 await 关键字,当它返回时,它不返回任务,而只是返回 T,因为整个主体将被重写为任务执行的状态机。
假设你有方法
public async Task DoSomething() {
}
当您从 UI 线程调用它时,您会返回一个任务。此时,您可以使用.Wait()
或.Result
阻止任务,这会在其任务计划程序上运行异步方法,或者阻止将在 UI 线程上运行该方法的.RunSynchronously()
。当然,DoSomething
内部发生的任何等待基本上都是另一个延续,因此它最终可能会在 TaskScheduler 线程上运行部分代码。但最终 UI 线程被阻塞直到完成,就像常规同步方法一样。
或者,您可以使用.ContinueWith()
计划继续,这将创建一个在任务完成时由任务计划程序调用的操作。这将立即将控制权返回给当前代码,该代码继续执行它在 UI 线程上执行的任何操作。延续不捕获调用堆栈,它只是一个操作,因此它仅捕获从其外部作用域访问的任何变量。