当异步方法完成时,等待被回调/执行的代码部分是如何执行的



我读过C#中的异步编程,但我仍然不完全理解异步方法的延续是如何执行的。根据我的理解,异步编程与多线程无关。我们可以在UI线程上运行异步方法,稍后它将在该UI线程上继续(同时不阻塞并继续响应消息循环中的其他消息(。

这是大多数GUI应用程序的基本消息循环:

while (1)
{
bRet = GetMessage(&msg, NULL, 0, 0);
if (bRet > 0)  // (bRet > 0 indicates a message that must be processed.)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
...

DispatchMessage()调用UI事件处理程序。事件处理程序内部的代码不应该阻塞主线程。因此,如果我们想,即创建一个从磁盘加载大量数据的按钮,我们可以使用这样的异步方法:(简化的伪代码(

public async Task ButtonClicked()
{
loadingBar.Show();
await AsyncLoadData();
loadingBar.Hide();
}

当执行到达await AsyncLoadData();行时,它存储上下文并返回Task对象。3结束并且消息循环重复地到达bRet = GetMessage(&msg, NULL, 0, 0);行。

所以我的问题是,其余的代码是如何执行的?完成的异步操作是否触发了一条新消息,然后由DispatchMessage()再次处理?或者消息循环有另一个方法(在调度之后(,用于检查已完成的异步操作?

所以我的问题是,其余代码是如何执行的?完成的异步操作是否触发了一条新消息,然后由DispatchMessage((再次处理?或者消息循环有另一个方法(在调度之后(,用于检查已完成的异步操作?

await默认情况下将捕获";上下文";并使用它来恢复该方法的执行。这个";上下文";是SynchronizationContext.Current,在TaskScheduler.Current上回落。UI应用程序提供SynchronizationContext,例如WindowsFormsSynchronizationContextDispatcherSynchronizationContext。当await完成时,它将方法的延续调度到该上下文上(在本例中,调度到SynchronizationContext上(。

对于WinForms,syncctx使用Control.BeginInvoke,它将发布由WinProc处理的Win32消息。

对于WPF,syncctx发布到其Dispatcher,CCD_15将回调添加到调度队列。此队列也由Win32 WinProc循环处理。

Alex Davies写了一本关于这方面的优秀著作,它被称为"C#5中的异步";,我强烈建议你读一读。我无法说出这背后的低级细节,但在高级CLR会创建这样的东西:

void __buttoncliked_remaining_code_1(...) { 
loadingBar.Hide(); 
} 

因此,将触发一个特定事件,指示异步作业已完成。然后__buttoncliked_remaining_code_1()将像任何常规的C#函数一样同步执行。CLR将使用任何线程,但很可能会重用遇到wait关键字的线程,在您的情况下可能是GUI线程。

最新更新