使用ContinueWith或异步等待时不同的行为 [英] Different behavior when using ContinueWith or Async-Await
问题描述
当我在电话的HttpClient使用异步等待的方法(如下面的例子),这code引起的僵局。更换用 t.ContinueWith
,能够正常工作的异步等待的方法。为什么呢?
公共类MyFilter:ActionFilterAttribute {
公共覆盖无效OnActionExecuting(ActionExecutingContext filterContext){
VAR用户= _authService.GetUserAsync(用户名)。结果;
}
}公共类AuthService:IAuthService {
公共异步任务<使用者> GetUserAsync(用户名字符串){
VAR jsonUsr =等待_httpClientWrp.GetStringAsync(URL).ConfigureAwait(假);
返回等待JsonConvert.DeserializeObjectAsync<使用者>(jsonUsr);
}
}
这工作:
公共类HttpClientWrapper:IHttpClient {
公共任务<串GT; GetStringAsync(字符串URL){
返回_client.GetStringAsync(URL).ContinueWith(T => {
_log.InfoFormat(回应:{0},网址);
返回t.Result;
});
}
这code就会死锁:
公共类HttpClientWrapper:IHttpClient {
公共异步任务<串GT; GetStringAsync(字符串URL){
字符串结果=等待_client.GetStringAsync(URL);
_log.InfoFormat(回应:{0},网址);
返回结果;
}
}
我描述这个僵局行为的在最近的MSDN文章我的博客和
-
的await
默认情况下将安排其延续到目前的的SynchronizationContext
,或中运行(如果没有的SynchronizationContext
)的电流的TaskScheduler
。 (在这里指的是ASP.NET请求的SynchronizationContext
)。 - 的ASP.NET
的SynchronizationContext
重新presents请求上下文和ASP.NET只允许一个线程在这种情况下,一次。
因此,HTTP请求完成时,它试图进入的SynchronizationContext
运行 InfoFormat
。但是,已经在的SynchronizationContext
线程 - 一个阻塞结果
(等待异步
的方法来完成)。
在另一方面,在默认情况下为 ContinueWith
默认行为,将安排其延续到目前的的TaskScheduler
(在这种情况下是线程池的TaskScheduler
)。
正如其他人所指出的那样,最好使用等待
一路,即不要在阻止异步
code。不幸的是,这不是因为 MVC不支持异步操作筛选一个在这种情况下的一个选项>(作为一个侧面说明,请投票在这里这种支持)。
所以,你的选择是使用 ConfigureAwait(假)
或仅使用同步方法。在这种情况下,我建议同步方法。 ConfigureAwait(假)
只能当工作
它适用于尚未完成,所以我建议,一旦你使用 ConfigureAwait(假)
,你应该在方法这一点后,使用它的每等待
(在这种情况下,在调用栈中的每个方法)。如果 ConfigureAwait(假)
正在使用效率的原因,那么这很好(因为它在技术上可选)。在这种情况下, ConfigureAwait(假)
将是必要的正确性的原因,所以国际海事组织它创建一个维护负担。同步方法会更清晰。
When I use an async-await method (as the example below) in a HttpClient call, this code causes a deadlock. Replacing the async-await method with a t.ContinueWith
, it works properly. Why?
public class MyFilter: ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var user = _authService.GetUserAsync(username).Result;
}
}
public class AuthService: IAuthService {
public async Task<User> GetUserAsync (string username) {
var jsonUsr = await _httpClientWrp.GetStringAsync(url).ConfigureAwait(false);
return await JsonConvert.DeserializeObjectAsync<User>(jsonUsr);
}
}
This works:
public class HttpClientWrapper : IHttpClient {
public Task<string> GetStringAsync(string url) {
return _client.GetStringAsync(url).ContinueWith(t => {
_log.InfoFormat("Response: {0}", url);
return t.Result;
});
}
This code will deadlock:
public class HttpClientWrapper : IHttpClient {
public async Task<string> GetStringAsync(string url) {
string result = await _client.GetStringAsync(url);
_log.InfoFormat("Response: {0}", url);
return result;
}
}
I describe this deadlock behavior on my blog and in a recent MSDN article.
await
will by default schedule its continuation to run inside the currentSynchronizationContext
, or (if there is noSynchronizationContext
) the currentTaskScheduler
. (Which in this case is the ASP.NET requestSynchronizationContext
).- The ASP.NET
SynchronizationContext
represents the request context, and ASP.NET only allows one thread in that context at a time.
So, when the HTTP request completes, it attempts to enter the SynchronizationContext
to run InfoFormat
. However, there is already a thread in the SynchronizationContext
- the one blocked on Result
(waiting for the async
method to complete).
On the other hand, the default behavior for ContinueWith
by default will schedule its continuation to the current TaskScheduler
(which in this case is the thread pool TaskScheduler
).
As others have noted, it's best to use await
"all the way", i.e., don't block on async
code. Unfortunately, that's not an option in this case since MVC does not support asynchronous action filters (as a side note, please vote for this support here).
So, your options are to use ConfigureAwait(false)
or to just use synchronous methods. In this case, I recommend synchronous methods. ConfigureAwait(false)
only works if the Task
it's applied to has not already completed, so I recommend that once you use ConfigureAwait(false)
, you should use it for every await
in the method after that point (and in this case, in each method in the call stack). If ConfigureAwait(false)
is being used for efficiency reasons, then that's fine (because it's technically optional). In this case, ConfigureAwait(false)
would be necessary for correctness reasons, so IMO it creates a maintenance burden. Synchronous methods would be clearer.
这篇关于使用ContinueWith或异步等待时不同的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!