我应该在所有方法中使用 configure await 还是只在第一种方法中使用?

Should I use configure await in all methods or only in the first method?

我有一个带有异步方法的库,我已经阅读了关于库的内容,建议使用 ConfigureAwait(false)

例如,如果我有这样的东西:

public async Task myMethod01()
{
    await myMethod02();
}

private async Task myMethod02()
{
    await myMethod03();
}

private async Task myMethod03()
{
    await anotherMetodAsync().ConfigureAwait(false);
}

库用户只能使用method01,其他2个方法是私有的,因为它们是主方法method01()的辅助方法method01()

但我不知道是否只需要在链调用的第一个方法中使用 ConfigureAwait 还是应该在所有方法中使用。

您应该在所有异步调用中使用 ConfigureAwait(false)。如果未完成此操作,第一个异步调用(没有 ConfigureAwait(false))将采用 SynchronizationContext,这可能会在您同步等待该调用时在某些情况下(如 ASP.NET)导致死锁。

我的建议是阅读 Stephen Cleary 撰写的 this article。您感兴趣的部分是:

Using ConfigureAwait(false) to avoid deadlocks is a dangerous practice. You would have to use ConfigureAwait(false) for every await in the transitive closure of all methods called by the blocking code, including all third- and second-party code. Using ConfigureAwait(false) to avoid deadlock is at best just a hack).

tl;博士

,需要它来确保库代码中的所有异步延续都在线程池线程上执行(取决于 SynchronizationContext/TaskScheduler使用中)。

想了解更多吗?

Task.ConfigureAwait(Boolean)

  • true 尝试将 异步方法的其余部分 编组回捕获的原始上下文
  • false 在线程池线程
  • 上安排 异步方法的剩余部分

考虑以下 WPF 示例: WPF 使用 DispatcherSynchronizationContext 在 UI 上下文中恢复异步延续,因为后台线程无法更新控件的内容。

private async void Button_Click(object sender, RoutedEventArgs e)
{
    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context

    await CompleteAsynchronously();

    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context

    await CompleteAsynchronously().ConfigureAwait(false);

    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //true
}

private async Task CompleteAsynchronously()
{
    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context

    await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false);

    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //true
}

在这里,您看到被调用方法的 continueOnCapturedContext 标志对调用者没有影响。然而,被调用的方法 运行s(或至少开始 运行)在调用者 运行 所在的线程上,当然。

但是,只有在等待未完成的任务时才会捕获当前上下文(或者当前 SynchronizationContext;如果为 null,则当前 TaskScheduler)。如果 Task 同步完成,continueOnCapturedContext 无效,方法的其余部分在当前线程上继续 运行 同步。

private async void Button_Click(object sender, RoutedEventArgs e)
{
    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context

    await CompleteSynchronously().ConfigureAwait(false);

    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context
}

private async Task CompleteSynchronously()
{
    await Task.Delay(0);
}

因此,在您的库代码中(假设您从不需要上下文),您应该始终使用 ConfigureAwait(false) 以确保不会为异步延续捕获上下文,无论框架调用您的程序集(例如 WPF、ASP.NET 核心、控制台……)。

有关详细信息,请查看此 MSDN Magazine Article by Stephen Cleary 中的 异步编程最佳实践 (i.a。ConfigureAwait)。 =24=]