假设我在主 UI 线程的嵌套函数中调用 await。此时线程会发生什么?控制是否返回到消息循环并且 UI 线程可以自由处理其他输入?
是的。当您await
是可等待的(例如 a )时,将捕获方法中Task<TResult>
线程的当前位置。async
然后,它会将方法的其余部分(“继续”)排队,以便在可等待对象完成时(例如,当 aTask<TResult>
完成时)执行。
但是,可以进行优化:如果 awaitable 已经完成,则await
不必等待,它会立即继续执行该方法。这称为“快速路径”,在此处进行了描述。
当等待的任务完成时,整个堆栈是否被推送到消息队列中,以便控制将通过每个嵌套函数返回,或者这里完全发生了其他事情?
线程的当前位置被推送到 UI 消息队列中。细节有点复杂:延续被安排在TaskScheduler.FromCurrentSynchronizationContext
除非SynchronizationContext.Current
是null
,在这种情况下它们被安排在TaskScheduler.Current
。此外,可以通过调用覆盖此行为,该调用ConfigureAwait(false)
始终在线程池上安排延续。由于SynchronizationContext.Current
是SynchronizationContext
用于 WPF/WinForms/Silverlight 的 UI,因此该延续确实会被推送到 UI 消息队列中。
其次(虽然我引起了你的注意),但我真的不明白为什么异步方法需要用 async 标记。不能异步执行任何方法吗?如果我想异步执行一个方法但它没有 async 关键字怎么办——有没有办法简单地做到这一点?
这些是“异步”一词的稍微不同的含义。async
关键字启用await
关键字。换句话说,async
方法可以await
。老式的异步委托(即BeginInvoke
/ EndInvoke
)与async
. 异步委托在ThreadPool
线程上执行,但async
方法在 UI 线程上执行(假设它们是从 UI 上下文调用的,而您没有调用ConfigureAwait(false)
)。
如果你想async
在一个线程上运行一个(非)方法ThreadPool
,你可以这样做:
await Task.Run(() => MyMethod(..));
我真正想知道的是延续在多大程度上继续......它是冻结整个调用堆栈,在任务完成时恢复它,还是只返回这么远?是否需要将函数本身标记为异步以支持继续,或者(正如我最初问的那样)它是否继续整个调用堆栈?
当前位置被捕获,并在继续运行时“恢复”。任何await
用于支持延续的函数都必须被标记async
。
如果async
从非async
方法调用方法,则必须直接处理Task
对象。通常不这样做。顶级async
方法可能会返回void
,因此没有理由不使用async
事件处理程序。
请注意,这async
纯粹是编译器转换。这意味着async
方法在编译后与常规方法完全相同。.NET 运行时不会以任何特殊方式处理它们。