当您有服务器端代码(即一些 ApiController
)并且您的函数是异步的 - 所以它们返回 Task<SomeObject>
- 任何时候等待调用 ConfigureAwait(false)
的函数是否被认为是最佳实践?
我读过它的性能更高,因为它不必将线程上下文切换回原始线程上下文。但是,对于 ASP.NET Web Api,如果您的请求来自一个线程,并且您等待某个函数并调用 ConfigureAwait(false)
,当您返回 ApiController
的最终结果时,这可能会将您置于另一个线程功能。
我在下面输入了一个我正在谈论的示例:
public class CustomerController : ApiController
{
public async Task<Customer> Get(int id)
{
// you are on a particular thread here
var customer = await GetCustomerAsync(id).ConfigureAwait(false);
// now you are on a different thread! will that cause problems?
return customer;
}
}
更新: ASP.NET Core does not have a SynchronizationContext
。如果您使用的是 ASP.NET Core,那么是否使用 ConfigureAwait(false)
都没有关系。
对于 ASP.NET “完整”或“经典”或其他任何内容,此答案的其余部分仍然适用。
原始帖子(针对非核心 ASP.NET):
This video by the ASP.NET team has the best information on using async
on ASP.NET.
我读过它的性能更高,因为它不必将线程上下文切换回原始线程上下文。
对于 UI 应用程序也是如此,其中只有一个 UI 线程需要“同步”回。
在 ASP.NET 中,情况要复杂一些。当 async
方法恢复执行时,它会从 ASP.NET 线程池中获取一个线程。如果您使用 ConfigureAwait(false)
禁用上下文捕获,那么线程将继续直接执行该方法。如果不禁用上下文捕获,那么线程将重新进入请求上下文,然后继续执行该方法。
所以 ConfigureAwait(false)
不会为您节省 ASP.NET 中的线程跳转;它确实为您节省了重新输入请求上下文的时间,但这通常非常快。 ConfigureAwait(false)
如果您尝试对请求进行少量并行处理,可能很有用,但实际上 TPL 更适合大多数情况。
但是,对于 ASP.NET Web Api,如果您的请求来自一个线程,并且您等待某个函数并调用 ConfigureAwait(false),当您返回 ApiController 函数的最终结果时,这可能会将您置于另一个线程.
实际上,只需执行 await
即可。一旦您的 async
方法遇到 await
,method 就会被阻塞,但 thread 会返回到线程池。当方法准备好继续时,从线程池中抢夺任何线程并用于恢复该方法。
ConfigureAwait
在 ASP.NET 中的唯一区别是该线程在恢复方法时是否进入请求上下文。
我的 MSDN article on SynchronizationContext
和 async
intro blog post 中有更多背景信息。
简要回答您的问题:不。您不应该像那样在应用程序级别调用 ConfigureAwait(false)
。
TL;DR 版本的长答案:如果您正在编写一个不了解您的使用者并且不需要同步上下文的库(我相信您不应该在库中),您应该始终使用 {1 }。否则,您的库的使用者可能会因为以阻塞方式使用您的异步方法而面临死锁。这取决于情况。
以下是关于 ConfigureAwait
方法重要性的更详细说明(引用自我的博客文章):
当您使用 await 关键字等待方法时,编译器会代表您生成一堆代码。此操作的目的之一是处理与 UI(或主)线程的同步。此功能的关键组件是 SynchronizationContext.Current,它获取当前线程的同步上下文。根据您所处的环境填充 SynchronizationContext.Current。Task 的 GetAwaiter 方法查找 SynchronizationContext.Current。如果当前同步上下文不为空,则传递给该等待者的延续将被回发到该同步上下文。当以阻塞方式使用使用新异步语言特性的方法时,如果您有可用的 SynchronizationContext,您将最终陷入死锁。当您以阻塞方式使用此类方法时(使用 Wait 方法等待 Task 或直接从 Task 的 Result 属性中获取结果),您将同时阻塞主线程。当任务最终在线程池中的该方法内完成时,它将调用延续以回发到主线程,因为 SynchronizationContext.Current 可用并被捕获。但是这里有个问题:UI线程被阻塞了,你就死锁了!
此外,这里有两篇非常适合您的文章,它们正是针对您的问题:
用 C# 5.0 异步语言特性解决死锁的完美秘诀
用于 HTTP API 的异步 .NET 客户端库以及对 async/await 不良影响的认识
最后,Lucian Wischik 提供了一个非常棒的短视频,正是关于这个主题的:Async library methods should consider using Task.ConfigureAwait(false)。
希望这可以帮助。
Task
遍历堆栈以获取 SynchronizationContext
,这是错误的。 SynchronizationContext
在调用 Task
之前被抓取,然后如果 SynchronizationContext.Current
不为 null,则其余代码在 SynchronizationContext
上继续。
SynchronizationContext.Current
清晰/或者在 Task.Run()
内调用库而不是在整个类库中编写 .ConfigureAwait(false)
?
.ConfigureAwait(false)
来表达。如果这是默认行为,库作者可能会更容易,但我认为让正确编写库变得更难一点比让正确编写应用程序更难一点要好。
我发现使用 ConfigureAwait(false) 的最大缺点是线程文化恢复为系统默认值。如果您配置了一种文化,例如...
<system.web>
<globalization culture="en-AU" uiCulture="en-AU" />
...
并且您托管在其文化设置为 en-US 的服务器上,那么您会在 ConfigureAwait(false) 被调用之前发现 CultureInfo.CurrentCulture 将返回 en-AU 并且在您将获得 en-US 之后。 IE
// CultureInfo.CurrentCulture ~ {en-AU}
await xxxx.ConfigureAwait(false);
// CultureInfo.CurrentCulture ~ {en-US}
如果您的应用程序正在执行任何需要对数据进行文化特定格式的操作,那么在使用 ConfigureAwait(false) 时需要注意这一点。
ConfigureAwait(false)
。
我对 Task
的实现有一些一般性的想法:
任务是一次性的,但我们不应该使用 using。 ConfigureAwait 是在 4.5 中引入的。任务是在 4.0 中引入的。 .NET 线程总是用于流动上下文(参见 C# via CLR 书),但在 Task.ContinueWith 的默认实现中,它们不是 b/c,它意识到上下文切换是昂贵的,默认情况下它是关闭的。问题是库开发人员不应该关心其客户是否需要上下文流,因此它不应该决定是否流上下文。 [稍后添加] 没有权威的答案和适当的参考,我们一直在为此奋斗,这意味着有人没有做好他们的工作。
我有几个关于这个主题的posts,但我的看法 - 除了 Tugberk 的好答案 - 您应该将所有 API 变为异步并理想地流动上下文。 由于您正在执行异步,您可以简单地使用延续而不是等待,因此不会导致死锁,因为库中没有等待,并且您保持流动,因此保留了上下文(例如 HttpContext)。
问题是库公开了同步 API 但使用了另一个异步 API - 因此您需要在代码中使用 Wait()
/Result
。
Task.Dispose
;你只是不需要绝大多数时间。 2) Task
作为 TPL 的一部分在 .NET 4.0 中引入,不需要 ConfigureAwait
;添加 async
后,他们重用了现有的 Task
类型,而不是发明了新的 Future
。
Task
s 中也是如此; ContinueWith
控制的“上下文”是 SynchronizationContext
或 TaskScheduler
。这些不同的上下文are explained in detail on Stephen Toub's blog。
Thread
一起流动的上下文,但不再与 ContinueWith()
一起流动),这会使您的答案难以阅读.
不定期副业成功案例分享
HttpContext.Current
由 ASP.NETSynchronizationContext
流动,当您await
时默认流动,但不由ContinueWith
流动。 OTOH,执行上下文(包括安全限制)是在 CLR 中通过 C# 提到的上下文,它由ContinueWith
和await
流动(即使您使用ConfigureAwait(false)
)。ConfigureAwait
实际上仅在您等待 tasks 时才有意义,而await
作用于任何“等待”。其他考虑的选项是:如果在库中,默认行为是否应该丢弃上下文?或者有默认上下文行为的编译器设置?这两个都被拒绝了,因为很难仅仅阅读代码并说出它的作用。ConfigureAwait(false)
来避免基于Result
/Wait
的死锁,因为在 ASP.NET 上,您首先不应该使用Result
/Wait
。