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

本文关键字:另一个 线程 继续 await ConfigureAwait false 没有 | 更新日期: 2023-09-27 18:09:58

我有一个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. 我如何说服继续在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在另一个线程上继续

我的理解是,如果我省略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

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

编辑:

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

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