如何在异步方法上使用ConfigureAwait

时间:2019-07-03 14:33:41

标签: c# asynchronous task

我正在研究ConfigureAwait的正确用法,我已经为ConfigureAwait确定了一个有效的用例(即,在等待之后不需要调用线程同步上下文),并且看不到任何使用建议ConfigureAwait关于异步方法

我的代码如下所示

Public async Task StartMyProgram()
{
     await RunBackgroundTask();
}

Private async Task RunBackgroundTask()
{
     await Task.Delay(5000);
}

要正确使用ConfigureAwait,我假设应该在两个await调用中都使用此代码,例如以下代码:

Public async Task StartMyProgram()
{
     await RunBackgroundTask().ConfigureAwait(false);
}

Private async Task RunBackgroundTask()
{
     await Task.Delay(5000).ConfigureAwait(false);
}

还是仅在私有RunBackgroundTask方法上需要它?

3 个答案:

答案 0 :(得分:2)

这是一种简化,但是您可以假设ConfigureAwait(false)是一种微妙的说法,“嘿,您将要调用的内容将无法获取当前的同步上下文”。

此处的关键字为current:同步上下文用于与异步状态机同步。您的异步方法变成了任务,并且只有当所有请求都完成时,才必须返回整个序列。 为了执行这种同步,内部任务计划程序需要同步上下文。在编写库时,您不知道调用方在做什么,特别是,您现在知道可能正在运行异步方法的其他线程(例如,不同线程中的并发异步方法或消息泵)。 因此,您可以安全调用ConfigureAwait(false),向运行时指示不要借用(并捕获)调用者同步上下文,而要使用新的上下文。

您为什么要这么做?首先,因为以不确定状态借用某些东西并不好。但是更重要的是,避免死锁:实际上,在执行异步方法期间,默认情况下,您正在使用捕获的调用方上下文。这意味着您可能会陷入死锁和/或细微问题,因为运行任务所需的线程可能会被您的方法卡住,从而最终陷入死锁。

默认情况下,当您使用异步/等待时,它将在启动请求的原始线程上恢复。但是,如果当前另一个长时间运行的进程接管了该线程,则您将被困在等待线程完成的过程中。为避免此问题,可以使用带有错误参数的名为ConfigureAwait的方法。当您这样做时,这告诉任务可以在任何可用线程上恢复自身,而不用等待最初创建它的线程。这样可以加快响应速度并避免许多死锁。

使用ConfigureAwait(true)(默认值),当您在另一个线程上继续执行时,线程同步上下文会丢失,从而失去文化和/或语言设置以及诸如HttpContext.Current之类的其他内容(这种情况在.NET中发生)标准)。

根据经验,应该始终在库代码中以及多线程时在代码中使用ConfigureAwait(false)。这是一个示例,因为默认行为可能不适用于大多数情况。

答案 1 :(得分:2)

  

还是仅在私有RunBackgroundTask方法上需要它?

每个方法应自行做出ConfigureAwait(false)决定。这是因为每个方法都在await处捕获其自己的上下文,而不管其调用者/被调用方法做什么。 ConfigureAwait配置单个await;它根本不会“流动”。

因此,RunBackgroundTask需要确定“我需要根据自己的情况继续吗?”如果否,则应使用ConfigureAwait(false)

StartMyProgram需要确定“我需要根据自己的情况继续吗?”如果否,则应使用ConfigureAwait(false)

答案 2 :(得分:0)

输入RunBackgroundTask时,您不知道SynchronizationContext是什么。因此,您实际上不需要捕获它,应该继续使用.ConfigureAwait(false)