我正在研究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
方法上需要它?
答案 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)
。