22

我一直在阅读新的 asyncawait关键字,这听起来很棒,但是在我迄今为止看过的任何介绍视频中,我都无法找到一个关键问题的答案(我还阅读了白皮书一会儿回来)。

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

当等待的任务完成时,整个堆栈是否被推送到消息队列中,以便控制将通过每个嵌套函数返回,或者这里完全发生了其他事情?

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

干杯。:)

编辑: 诚然,如果我可以编译示例代码,我可能会自己弄清楚,但由于某种原因,我在那里遇到了障碍。我真正想知道的是延续在多大程度上继续......它是冻结整个调用堆栈,在任务完成时恢复它,还是只返回这么远?是否需要将函数本身标记为异步以支持继续,或者(正如我最初问的那样)它是否继续整个调用堆栈?

如果它没有冻结整个调用堆栈,那么当异步等待遇到非异步调用函数时会发生什么?它在那里阻塞吗?这不会破坏等待的意义吗?我希望您能看到我在这里遗漏了一些理解,希望有人可以填写,以便我可以继续学习。

4

2 回答 2

26

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

是的。当您await是可等待的(例如 a )时,将捕获方法中Task<TResult>线程的当前位置。async然后,它会将方法的其余部分(“继续”)排队,以便在可等待对象完成时(例如,当 aTask<TResult>完成时)执行。

但是,可以进行优化:如果 awaitable 已经完成,则await不必等待,它会立即继续执行该方法。这称为“快速路径”,在此处进行了描述

当等待的任务完成时,整个堆栈是否被推送到消息队列中,以便控制将通过每个嵌套函数返回,或者这里完全发生了其他事情?

线程的当前位置被推送到 UI 消息队列中。细节有点复杂:延续被安排在TaskScheduler.FromCurrentSynchronizationContext除非SynchronizationContext.Currentnull,在这种情况下它们被安排在TaskScheduler.Current。此外,可以通过调用覆盖此行为,该调用ConfigureAwait(false)始终在线程池上安排延续。由于SynchronizationContext.CurrentSynchronizationContext用于 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 运行时不会以任何特殊方式处理它们。

于 2011-08-30T13:50:14.660 回答
4

这取决于 Awaitable 的行为。

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

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

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

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

假设你有方法

public async Task DoSomething() {
}

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

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

于 2011-08-11T20:26:46.900 回答