最佳实践是对所有服务器端代码调用ConfigureAwait。

9 浏览
0 Comments

最佳实践是对所有服务器端代码调用ConfigureAwait。

当你有服务器端代码(比如某个ApiController)且你的函数是异步的 - 所以它们返回Task - 是否被认为是最佳实践,每次你等待调用的函数时都要使用ConfigureAwait(false)?\n我曾读到过这样做更高效,因为它不必切换线程上下文回到原始线程上下文。然而,对于ASP.NET Web Api而言,如果你的请求在一个线程上进行,而你等待某个函数并调用了ConfigureAwait(false),那么当你返回ApiController函数的最终结果时,可能会将你放在另一个线程上。\n以下是我所讲述的例子:\n

public class CustomerController : ApiController
{
    public async Task Get(int id)
    {
        // 你在这里是在特定的线程上
        var customer = await GetCustomerAsync(id).ConfigureAwait(false);
        // 现在你在另一个线程上!那会造成问题吗?
        return customer;
    }
}

0
0 Comments

在使用ConfigureAwait(false)时,我发现最大的问题是线程的文化设置会被还原为系统默认值。如果你已经配置了一个文化设置,比如:

<system.web>
    <globalization culture="en-AU" uiCulture="en-AU" />    
    ...

但是你的应用程序运行在一个文化设置为en-US的服务器上,那么在调用ConfigureAwait(false)之前,CultureInfo.CurrentCulture将返回en-AU,之后将返回en-US。

也就是说:

// CultureInfo.CurrentCulture ~ {en-AU}
await xxxx.ConfigureAwait(false);
// CultureInfo.CurrentCulture ~ {en-US}

如果你的应用程序需要对数据进行特定文化的格式化,则在使用ConfigureAwait(false)时需要注意这一点。

现代版本的.NET(我认为从4.6开始)即使使用了ConfigureAwait(false),也会在线程间传播文化设置。

感谢提供的信息。我们确实使用的是.NET 4.5.2版本。

0
0 Comments

问题的原因是为了避免在使用异步方法时出现死锁的情况。解决方法是在库的代码中使用ConfigureAwait(false),以确保不会阻塞主线程。

文章标题:server-side代码中调用ConfigureAwait的最佳实践

问题:为什么要在server-side代码中调用ConfigureAwait?以及如何解决这个问题?

在server-side代码中调用ConfigureAwait的最佳实践是为了避免在使用异步方法时出现死锁的情况。当我们在等待一个使用await关键字的方法时,编译器会为我们生成一些代码,其中一个目的是处理与UI(或主)线程的同步。这个特性的关键组件是SynchronizationContext.Current,它获取当前线程的同步上下文。

当我们以阻塞方式使用这些方法时(使用Wait方法等待任务或直接使用Task的Result属性获取结果),我们会同时阻塞主线程。当任务在线程池中完成时,它会调用继续方法以将结果返回到主线程,因为此时SynchronizationContext.Current是可用的。然而,这里面存在一个问题:UI线程被阻塞了,导致死锁的产生。

为了解决这个问题,我们可以在库的代码中使用ConfigureAwait(false)。这样一来,我们就能确保在调用异步方法时不会阻塞主线程,从而避免死锁的发生。

如果我们在写一个库时不知道使用者,也不需要同步上下文(我认为在库中不需要同步上下文),那么我们应该始终使用ConfigureAwait(false)。否则,库的使用者可能会面临通过阻塞方式使用异步方法而导致的死锁问题。这取决于具体情况。

这里还有一些更详细的解释,关于ConfigureAwait方法的重要性(摘自我的博客文章):

“当你使用新的异步语言特性时等待一个方法时,编译器会为你生成一些代码。其中一个目的是处理与UI(或主)线程的同步。这个特性的关键组件是SynchronizationContext.Current,它获取当前线程的同步上下文。GetAwaiter方法会查找SynchronizationContext.Current。如果当前同步上下文不为空,那么传递给awaiter的继续方法将会被发布到该同步上下文中。

当以阻塞方式使用使用新的异步语言特性的方法时,如果有一个可用的同步上下文,那么就会出现死锁。当以阻塞方式使用这些方法时(使用Wait方法等待任务或直接使用Task的Result属性获取结果),你将同时阻塞主线程。当任务在线程池中完成时,它将会调用继续方法将结果发布到主线程,因为SynchronizationContext.Current是可用的且被捕获了。但是这里有一个问题:UI线程被阻塞了,所以会导致死锁的产生!”

所以,在server-side代码中调用ConfigureAwait的最佳实践是为了避免在使用异步方法时出现死锁的情况。如果我们在写一个库时不知道使用者,也不需要同步上下文,我们应该始终使用ConfigureAwait(false)。这样一来,我们就能确保在调用异步方法时不会阻塞主线程,从而避免死锁的发生。

如果你想了解更多关于这个问题的内容,以下是两篇与此问题相关的优秀文章:

- The Perfect Recipe to Shoot Yourself in The Foot - Ending up with a Deadlock Using the C# 5.0 Asynchronous Language Features

- Asynchronous .NET Client Libraries for Your HTTP API and Awareness of async/await's Bad Effects

另外,还有一段非常好的视频讲解了这个主题:

- Async library methods should consider using Task.ConfigureAwait(false)

最后,有一个关于这个问题的很好的问题和回答:

- 问:是否可以只在顶层使用ConfigureAwait,而不需要在每个调用中都添加?如果忘记在一个调用上使用ConfigureAwait,会导致主线程死锁的情况。

答:你需要在库级别的每个调用上都使用它。

希望通过这篇文章能够帮助你了解到在server-side代码中调用ConfigureAwait的最佳实践。

0
0 Comments

在ASP.NET中使用async/await时,可以通过调用ConfigureAwait(false)来提高性能。在UI应用程序中,这样做是有意义的,因为不需要切换线程上下文。但是在ASP.NET中,情况就复杂了一些。当一个async方法恢复执行时,它会从ASP.NET线程池中获取一个线程。如果使用ConfigureAwait(false)禁用上下文捕获,则线程会直接继续执行该方法。如果不禁用上下文捕获,则线程将重新进入请求上下文,然后继续执行该方法。因此,在ASP.NET中,ConfigureAwait(false)并不会减少线程的跳转次数,但它确实可以避免重新进入请求上下文,尽管这通常非常快。在大多数情况下,TPL是更好的选择,而不是使用ConfigureAwait(false)进行并行处理请求。

另外,当在ASP.NET Web Api中调用ConfigureAwait(false)时,可能会在返回ApiController函数的最终结果时将你放在不同的线程上。

在ASP.NET Core中,不管是否使用ConfigureAwait(false),都不会出现这个问题,因为它没有同步上下文。但是在ASP.NET "Full"或者"Classic"中,这个问题仍然存在。

需要注意的是,在ASP.NET Core中,如果使用自定义的SynchronizationContext或TaskScheduler,那么ConfigureAwait(false)就可以避免它们。但是默认情况下,ASP.NET Core没有这样的上下文,如果你添加了它们,那么你会知道是否需要避免它们。

总结起来,如果你在ASP.NET中使用async/await,可以考虑在所有的服务器端代码中调用ConfigureAwait(false)来提高性能。但是在ASP.NET Core中,不需要调用ConfigureAwait(false)。

文章的代码如下:

Update: ASP.NET Core does not have a SynchronizationContext. If you are on ASP.NET Core, it does not matter whether you use ConfigureAwait(false) or not.
For ASP.NET "Full" or "Classic" or whatever, the rest of this answer still applies.
Original post (for non-Core ASP.NET):
This video by the ASP.NET team has the best information on using async on ASP.NET.
I had read that it is more performant since it doesn't have to switch thread contexts back to the original thread context.
This is true with UI applications, where there is only one UI thread that you have to "sync" back to.
In ASP.NET, the situation is a bit more complex. When an async method resumes execution, it grabs a thread from the ASP.NET thread pool. If you disable the context capture using ConfigureAwait(false), then the thread just continues executing the method directly. If you do not disable the context capture, then the thread will re-enter the request context and then continue to execute the method.
So ConfigureAwait(false) does not save you a thread jump in ASP.NET; it does save you the re-entering of the request context, but this is normally very fast. ConfigureAwait(false) could be useful if you're trying to do a small amount of parallel processing of a request, but really TPL is a better fit for most of those scenarios.
However, with ASP.NET Web Api, if your request is coming in on one thread, and you await some function and call ConfigureAwait(false) that could potentially put you on a different thread when you are returning the final result of your ApiController function.
Actually, just doing an await can do that. Once your async method hits an await, the method is blocked but the thread returns to the thread pool. When the method is ready to continue, any thread is snatched from the thread pool and used to resume the method.
The only difference ConfigureAwait makes in ASP.NET is whether that thread enters the request context when resuming the method.
I have more background information in my MSDN article on SynchronizationContext and my async intro blog post.
My answer got deleted so cannot answer you there. But I am not confusing contexts here, I do not know about you. What is meant by context is Thread Storage Area data. The context does not flow in ContinueWith by default - period. TSA data does not get copied - please prove me wrong if you think otherwise You can check this by looking at HttpContext.Current. That is why we go through hoops and hoops to flow that.
Thread-local storage isn't flowed by any context. HttpContext.Current is flowed by the ASP.NET SynchronizationContext, which is flowed by default when you await, but it's not flowed by ContinueWith. OTOH, the execution context (including security restrictions) is the context mentioned in CLR via C#, and it is flowed by both ContinueWith and await (even if you use ConfigureAwait(false)).
Thank you Stephen, I marked your post as the answer. I had to read it a few times to get it, but it seems like the only time it would ever be useful to call ConfigureAwait(false) is in a desktop/mobile app, where you make an asynchronous call (like a HttpWebRequest) and would rather do the processing of the result off the UI thread. Otherwise, it is not worth cluttering up the code for any small performance gains made when using ASP.NET.
Wouldn't it be great if C# had native language support for ConfigureAwait(false)? Something like 'awaitnc' (await no context). Typing out a separate method call everywhere is pretty annoying. :)
It was discussed quite a bit. The problem with a new keyword is that ConfigureAwait actually only makes sense when you await tasks, whereas await acts on any "awaitable." Other options considered were: Should the default behavior discard context if in a library? Or have a compiler setting for the default context behavior? Both of these were rejected because it's harder to just read the code and tell what it does.
I don't understand your line : If you disable the context capture using ConfigureAwait(false), then the thread just continues executing the method directly. — Are you saying that the thread is not back in the threadpool , but still waits till the operation is finished , and when it does , it continues the callback ? ( with the same thread)......— Or - Are you talking about that when the async operation finished , another/same thread from threadpool is back to continue the callback , but it's just doesnt enter the execution context....
When the async operation finishes, a thread from the thread pool is used to actually complete the task. If you use ConfigureAwait(false), then that same thread resumes executing the async method without entering the request context. (BTW, this is an implementation detail; this behavior is undocumented).
So does this means that in webapi controller we should not use ConfigureAwait(false)?
You should use ConfigureAwait(false) whenever you don't need the request context.
But is it very unlikely to not to use request context because for every request one need to send response , something like this.Request.CreateResponse(HttpStatusCode.xxx)
Which is why controller actions need their context. But most methods that the actions call do not.
Isn't it worth mentioning your other points on deadlocks? stackoverflow.com/questions/13140523?rq=1
Generally speaking, you shouldn't need ConfigureAwait(false) to avoid a Result/Wait-based deadlock because on ASP.NET you should not be using Result/Wait in the first place.
Do I understand correctly from your blog A good rule of thumb is to use ConfigureAwait(false) unless you know you do need the context that using ConfigureAwait(false) will give a performance edge because the current context (e.g. ASP.Net context) is not flowed, but leaving ConfigureAwait(false) out should cause no functional impact?
In some cases it can give you better performance. It'll never give you worse performance. There's no functional impact unless someone is using ConfigureAwait(false) as part of a sync-over-async hack.
I didn't want to ask it here in the comments: stackoverflow.com/questions/33711136
You've already got two good answers. The reason it's not behaving as you expect is because your inner "asynchronous" method is actually synchronous (and the compiler explicitly warns you about this). Make it truly asynchronous (e.g., add an await Task.Delay(200);), and you'll see the thread returned to the thread pool and 200ms later a new thread taken from the thread pool to resume the method.
As I can see, the root cause is in improper using of Result/Wait, where ConfigureAwait(false) acts as a workaround. So why so many folks suggest to put this workaround everywhere instead of just fixing the cause. For example, using Task.Run(async () => { await ...}).Wait(); will do the trick and this is easier than putting 100 times ConfigureAwait(false) everywhere in library code. Why does no one suggest it?
This is an example resharper-plugins.jetbrains.com/packages/ConfigureAwaitChecker
I'm confused about your comments: "You should use ConfigureAwait(false) whenever you don't need the request context.", but earlier you say "HttpContext.Current is flowed by the ASP.NET SynchronizationContext, which is flowed by default when you await". So any method has access to the request context regardless? When I test it, I can always access HttpContext.Request in my methods, no matter if I call ConfigureAwait(false) or not.
The "by default" means "unless you use ConfigureAwait(false)". HttpContext.Request is not going through HttpContext.Current; HttpContext.Current is a static property. Also, note that HttpContext is not safe for multithreaded use; if you use ConfigureAwait(false), you can access HttpContext.Request, but you definitely shouldn't.
Oh misinterpreted your comment, my bad. Thanks for clearing it up
Any thoughts on what neleus suggested as an alternate workaround?
It only works if the code executed by the Task.Run doesn't depend on the current context (e.g., HttpContext.Current, any ASP.NET APIs - some of which implicitly depend on an ASP.NET context, dependency injection resolution that is scoped to a request, etc). And keep in mind that "works" in this scenario means "wastes a thread". await is still the best solution.
Yes, but if you are running any async code that needs the context and thus wouldn't have ConfigureAwait(false), then you are hit by the deadlock anyway. Both approaches don't work in that case or am I missing something?
The best way to avoid a deadlock is to not block on async code at all.
I couldn't agree more. After re-reading the whole thread I can see I didn't set the scenario well. What I get now is: the reason to put it everywhere is to avoid unnecessarily restoring the context, which can give you some (small) gain in performance (so no for the deadlocks). Some people use it for deadlocks when calling it from non async code (typically in big code bases upgrade where it is not possible to fully move to async in one go). It is for this later case that I was comparing ConfigureAwait(false) everywhere to a single Task.Run, as it is easy for it to be missed and end up wrong.
I had the same issue with my ASP.NET webform app.My Question states the problem but I can't find any solution. I used ConfigureAwait(false) with every await and it worked but when the application runs for the first time page loads as expected but if we request the page again page never loads again. Can help on this matter will be fruitful. Thanks
I don't know if WebMethod supports async. I've never used asmx. (Note: WebMethod is asmx, not WebForms).
I have defined WebMethod in my code behind file, every aspx page has tons of WebMethod in its code behind file. [WebMethod] public static async Task> GetXXX() => await new BusinessLogic().GetXXX().ConfigureAwait(false); Thak you very much for the input, but as I am in a scenario can you please give me any recommendations.
My recommendation would be to move from asmx to WebAPI.
Please update this post to include content from your recent post blog.stephencleary.com/2017/03/…
One thing missing from this answer is Globalisation and Culture. Using ConfigureAwait(false) loses the web.config system.web/globalization settings. See my answer below for more details.
What about Xamarin? It's same as ASP.NET Core in this case?
No. UI frameworks including Xamarin have a synchronization context, and code must be in that context to access UI elements.
The whole topic is so vague and misleading. Even you all need to step back and realize there are far so many other scenarios that can cause deadlock and this is still a huge issue. is confident but the entire framework is whack. in .net core or not. shame
Your first sentence "If you are on ASP.NET Core, it does not matter whether you use ConfigureAwait(false) or not" doesn't hold true. Check this article (devblogs.microsoft.com/dotnet/configureawait-faq): Whereas the classic ASP.NET on .NET FW has its own SynchronizationContext, in contrast ASP.NET Core does not. That means that code running in an Core app won’t see a custom SynchronizationContext, which lessens the need for ConfigureAwait(false) running in such an environment. It doesn’t mean, however, that there will never be a custom SynchronizationContext or TaskScheduler present.
Yes, if you are using a custom SynchronizationContext or TaskScheduler, then ConfigureAwait(false) will allow you to avoid it. But by default, there is no such thing on ASP.NET Core, and if you add it, then you will also know whether you need to avoid it. FWIW, I don't know of anyone who has used a custom SyncCtx or TaskSched on ASP.NET Core.
Careful people. not all of ASP.NET Core "does not have synchronization context"!! SignalR, for example, does have one, so you will get deadlocks without ConfigureAwait(false)
Nice answer Stephen. Btw the video link doesn't work anymore.

0