繁体   English   中英

没有ConfigureAwait(false)的await在另一个线程上继续

[英]await without ConfigureAwait(false) continues on a different thread

我有一个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) ,那么继续应该在原始线程上运行。 在某些情况下这是不正确的吗?

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

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. 我如何说服继续在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.
}

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

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

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

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

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

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

\n

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

编辑:

我发现了真正的问题:事实证明我在ConfigureAwait(false)之后调用了Form.ShowDialog() ConfigureAwait(false) 因此,表单是在UI线程上构建的,但后来我在非UI线程上调用ShowDialog 我很惊讶这一点起作用了。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM