ConfigureAwait 在 .NET 中的使用

Usage of ConfigureAwait in .NET

我在很多地方读过 ConfigureAwait(包括 SO 问题),这里是我的结论:

我的问题是:

  1. 我的结论正确吗?
  2. ConfigureAwait(false) 何时提高性能,何时提高性能?
  3. 如果为 GUI 应用程序编写,但下一行不访问 UI 元素。我应该使用 ConfigureAwait(false) 还是 ConfigureAwait(true)?

ConfigureAwait(false) 如果可用的工作线程不多并且需要等待的线程一直很忙,则可能会提高性能。

ConfigureAwait(false) 在任何不需要返回相同 SynchronizationContext(通常与线程链接)的地方都被推荐,尤其是在内部等待某些东西的库中:https://medium.com/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f.

ConfigureAwait(true)(默认设置)在您需要相同的上下文时是必需的,但在某些情况下也可能导致死锁。

考虑这段代码:

void Main()
{
    // creating a windows form attaches a synchronization context to the current thread
    new System.Windows.Forms.Form();
    var task = DoSth();
    Console.WriteLine(task.Result);
}

async Task<int> DoSth()
{
    await Task.Delay(1000);
    return 1;
}

在此示例中,由于未等待任务 DoSth,主 UI 线程因等待 task.Result 而被阻塞 - 同时 DoSth 被阻塞,因为它想要返回到 UI延迟后的线程。这会导致死锁,这段代码永远不会执行到最后。添加 .ConfigureAwait(false) 解决了这种情况下的问题。

在应用程序代码中使用 ConfigureAwait(false) 通常不会以任何有意义的方式提升应用程序的性能,因为通常您不会 await 在应用程序代码的内部循环中。例如,假设您的应用程序有一个按钮,每次用户单击该按钮时都会启动一个异步操作,并且该异步操作包含一个 await。通过在此 await 之后键入 22 个字符 .ConfigureAwait(false),您已经失去了可比的生命时间,而您希望从 10 个每分钟单击此按钮一次的用户中节省时间,即每天 8 小时,每次 20 年(总共约 35,000,000 次上下文切换 = CPU 处理时间的几秒)。

而这之前考虑到你需要考虑是否可以安全地包含这个配置的时间(取决于continuation是否包含UI相关代码),你需要的时间是为了每次你必须 maintain/modify 代码时重新确认你以前的评估,以及你在调试时会损失的时间,以防你的评估错误。

另一方面,如果您的 Button_Click 处理程序包含如下代码:

private async void Button_Click(object sender, EventArgs e)
{
    var client = new WebClient();
    using var stream = await client.OpenReadTaskAsync("someUrl");
    var buffer = new byte[1024];
    while ((await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
    {
        //...
    }
}

...那么一定要花额外的时间来 ConfigureAwait(false) ReadAsync 任务。还要考虑通过将流读取部分移动到单独的异步方法来重构代码,这样您就可以安全地访问 Button_Click 处理程序内任何位置的 UI 元素,而不会被不包含的技术分心属于app的这一层。

更直接地回答您的问题:

ConfigureAwait(true): Runs the rest of the code on the same thread the code before the await was run on.

不一定是同一个线程,而是同一个同步上下文。同步上下文可以决定如何 运行 代码。在 UI 应用程序中,它将是同一个线程。在 ASP.NET 中,它可能不是同一个线程,但您将有 HttpContext 可用,就像以前一样。

ConfigureAwait(false): Runs the rest of the code on the same thread the awaited code was run on.

这是不正确的。 ConfigureAwait(false) 告诉它不需要上下文,所以代码可以是 运行 anywhere。它可以是 运行 的任何线程。

If the await is followed by a code that accesses the UI, the task should be appended with .ConfigureAwait(true). Otherwise, an InvalidOperationException will occur due to another thread accessing UI elements.

“应该附加.ConfigureAwait(true)”是不正确的。 ConfigureAwait(true) 是默认值。所以如果那是你想要的,你不需要指定它。

  1. When does ConfigureAwait(false) improves performance, and when it doesn't?

返回同步上下文可能需要一些时间,因为它可能必须等待其他事情完成 运行ning。实际上,这种情况很少发生,或者说等待时间微乎其微,您根本不会注意到。

  1. If writing for a GUI application, but the next lines doesn't access the UI elements. Should I use ConfigureAwait(false) or ConfigureAwait(true) ?

可以使用ConfigureAwait(false),但我建议您不要使用,原因如下:

  1. 我怀疑您会注意到任何性能改进。
  2. 它可以引入您可能意想不到的并行性。如果您使用 ConfigureAwait(false),延续可以在任何线程上 运行,因此如果您正在访问非线程安全对象,您可能会遇到问题。出现这些问题并不常见,但它可能会发生。
  3. 您(或维护此代码的其他人)稍后可以添加与 UI 交互的代码,并且会抛出异常。希望 ConfigureAwait(false) 很容易被发现(它可能与抛出异常的方法不同)并且 you/they 知道它的作用。

我发现完全不使用 ConfigureAwait(false) 更容易(图书馆除外)。用 ConfigureAwait FAQ:

中 Stephen Toub(微软员工)的话来说

When writing applications, you generally want the default behavior (which is why it is the default behavior).

编辑: 我自己写了一篇关于这个主题的文章:.NET: Don’t use ConfigureAwait(false)