我读过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
,例如WindowsFormsSynchronizationContext
或DispatcherSynchronizationContext
。当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线程。