【问题标题】:await without ConfigureAwait(false) continues on a different thread没有 ConfigureAwait(false) 的等待在不同的线程上继续
【发布时间】:2015-11-19 14:32:38
【问题描述】:

我有一个 WinForms 应用程序,并且我有一些代码需要在 UI 线程上运行。但是,await 之后的代码在不同的线程上运行。

protected override async void OnHandleCreated(EventArgs e)
{
    base.OnHandleCreated(e);

    // This runs on the UI thread.
    mainContainer.Controls.Clear();

    var result = await DoSomethingAsync();

    // This also needs to run on the UI thread, but it does not.
    // Instead it throws an exception:
    // "Cross-thread operation not valid: Control 'mainContainer' accessed from a thread other than the thread it was created on"
    mainContainer.Controls.Add(new Control());
}

我也尝试过显式添加ConfigureAwait(true),但这并没有什么区别。我的理解是,如果我省略ConfigureAwait(false),那么继续应该在原始线程上运行。在某些情况下这是不正确的吗?

我还注意到,如果我在等待之前将控件添加到集合中,那么延续会神奇地在正确的线程上运行。

protected override async void OnHandleCreated(EventArgs e)
{
    base.OnHandleCreated(e);

    // This runs on the UI thread.
    mainContainer.Controls.Add(new Control());
    mainContainer.Controls.Clear();

    var result = await DoSomethingAsync();

    // This also runs on the UI thread now. Why?
    mainContainer.Controls.Add(new Control());
}

我的问题是:

  1. 为什么会这样?
  2. 我如何说服 continuation 在 UI 线程上运行(最好不要像我那样添加和删除控件)?

作为参考,这里是DoSomethingAsync的重要部分。它使用 RestSharp 提交 HTTP 请求。

protected async Task DoSomethingAsync()
{
    IRestRequest request = CreateRestRequest();

    // Here I await the response from RestSharp.
    // Client is an IRestClient instance.
    // I have tried removing the ConfigureAwait(false) part, but it makes no difference.
    var response = await Client.ExecuteTaskAsync(request).ConfigureAwait(false);

    if (response.ResponseStatus == ResponseStatus.Error)
        throw new Exception(response.ErrorMessage ?? "The request did not complete successfully.");

    if (response.StatusCode >= HttpStatusCode.BadRequest)
        throw new Exception("Server responded with an error: " + response.StatusCode);

    // I also do some processing of the response here; omitted for brevity.
    // There are no more awaits.
}

【问题讨论】:

  • 为什么你认为它在不同的线程上运行?这段代码在哪里运行?事件处理程序?
  • 我知道它在不同的线程上运行,因为等待之后的代码抛出异常:“跨线程操作无效:控件'mainContainer'从创建它的线程以外的线程访问"。
  • 是的,代码在事件处理程序中运行。我会更新我的问题。
  • OnHandleCreated 注册到哪个事件?因为winforms中的事件处理程序通常有2个参数。
  • HandleCreated 事件肯定涉及到黑魔法。我改用Load 事件,一切正常。无论如何,我不确定为什么要使用HandleCreated。 (我从别人那里继承了这段代码,而且很多很乱)。

标签: c# .net winforms async-await


【解决方案1】:

OnHandleCreated 似乎发生了一些奇怪的事情。我的解决方案是改用OnLoad。我对这个解决方案非常满意,因为在我的情况下确实没有理由使用 OnHandleCreated

我仍然很好奇为什么会发生这种情况,所以如果有人知道,请随时发布另一个答案。

编辑:

我发现了真正的问题:原来我在ConfigureAwait(false) 之后调用了Form.ShowDialog()。因此,表单是在 UI 线程上构建的,但后来我在非 UI 线程上调用 ShowDialog。我很惊讶这完全有效。

我已经删除了 ConfigureAwait(false),所以现在 ShowDialog 在 UI 线程上被调用。

【讨论】:

  • 你能把Debug.WriteLine(new { SynchronizationContext.Current })放在await DoSomethingAsync()之前的OnHandleCreated里面吗?它显示System.Windows.Forms.WindowsFormsSynchronizationContext(预期)还是System.Threading.SynchronizationContext
  • 我在等待之前得到一个System.Threading.SynchronizationContext,在等待之后得到一个空值。我不确定为什么昨天我得到了一个非空值。但我想这可以解释它。我还注意到,如果我在我们 repo 的不同分支上的同一个项目中运行相同的代码,我会得到一个System.Windows.Forms.WindowsFormsSynchronizationContext,并且一切正常。我想知道它是否与表单的初始化方式有关。
  • 通过您最近的编辑,您应该将您的答案标记为已接受,因为它解释了实际发生的情况。否则,在OnHandleCreated 中使用await 是安全的,请参阅我的comment 对斯蒂芬的回答。
  • @Noseratio 是的,我必须等待 2 天才能接受我自己的答案。一旦允许我,我会接受它:)
【解决方案2】:

我的理解是,如果我省略 ConfigureAwait(false),那么延续应该在原始线程上运行。在某些情况下这是不正确的吗?

实际发生的是await默认会捕获当前上下文,并使用这个上下文来恢复async方法。这个上下文是SynchronizationContext.Current,除非它是null,在这种情况下它是TaskScheduler.Current(通常是线程池上下文)。大多数时候,UI 线程都有一个 UI SynchronizationContext - 在 WinForms 的情况下,WinFormsSynchronizationContext 的一个实例。

我还注意到,如果我在等待之前将控件添加到集合中,那么延续会神奇地在正确的线程上运行。

没有线程自动以SynchronizationContext 开头。 WinForms SynchronizationContext 在创建第一个控件时按需安装。这就是为什么您在创建控件后看到它在 UI 线程上恢复的原因。

由于迁移到 OnLoad 是一个可行的解决方案,我建议您直接使用它。唯一的其他选择(在创建控件之前在 UI 线程上恢复)是在您的第一个 await 之前手动创建一个控件。

【讨论】:

  • 实际上,WindowsFormsSynchronizationContext 已安装在 Control 的构造函数 here 中,因此在调用 OnHandleCreated 时它应该已经存在。这必须是别的东西。有一个可能相关的bug,但在这种情况下OnHandleCreatedOnLoad 都会受到影响。
猜你喜欢
  • 1970-01-01
  • 2019-04-26
  • 2017-08-03
  • 1970-01-01
  • 1970-01-01
  • 2018-05-28
  • 1970-01-01
  • 2013-04-25
相关资源
最近更新 更多