【问题标题】:How does awaiting part of code calledback/executed when asynchronous method finished异步方法完成时如何等待部分代码回调/执行
【发布时间】:2020-12-17 21:37:17
【问题描述】:

我读过 C# 中的异步编程,但我仍然不完全理解如何执行 async 方法的延续。 据我了解,异步编程与多线程无关。我们可以在 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 对象。 DispatchMessage() 结束,消息循环反复到达bRet = GetMessage(&msg, NULL, 0, 0); 行。

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

【问题讨论】:

标签: c# asynchronous async-await


【解决方案1】:

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

await 默认情况下将捕获“上下文”并使用它来恢复方法的执行。这个“上下文”是SynchronizationContext.Current,依赖TaskScheduler.Current。 UI 应用程序提供SynchronizationContext,例如WindowsFormsSynchronizationContextDispatcherSynchronizationContext。当await 完成时,它会将方法的延续安排到该上下文(在本例中为SynchronizationContext)。

对于 WinForms,syncctx uses Control.BeginInvoke,即post a Win32 message,即handled by the WinProc

对于 WPF,syncctx posts to its Dispatcheradds the callback to the dispatch queue。这个队列也是processed by a Win32 WinProc loop

【讨论】:

    【解决方案2】:

    Alex Davies 在这方面写了一本很棒的书,叫做“Async in C# 5”,我强烈推荐阅读它。 我无法说出这背后的低级细节,但在高级 CLR 会创建如下内容:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-08
      • 2021-11-13
      • 2013-02-15
      • 1970-01-01
      相关资源
      最近更新 更多