Dou*_* M. 7 .net c# asp.net-mvc multithreading async-await
当我在HttpClient调用中使用async-await方法(如下例所示)时,此代码会导致死锁.用a替换async-await方法t.ContinueWith,它可以正常工作.为什么?
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这有效:
public class HttpClientWrapper : IHttpClient {
public Task<string> GetStringAsync(string url) {
return _client.GetStringAsync(url).ContinueWith(t => {
_log.InfoFormat("Response: {0}", url);
return t.Result;
});
}
Run Code Online (Sandbox Code Playgroud)
这段代码会死锁:
public class HttpClientWrapper : IHttpClient {
public async Task<string> GetStringAsync(string url) {
string result = await _client.GetStringAsync(url);
_log.InfoFormat("Response: {0}", url);
return result;
}
}
Run Code Online (Sandbox Code Playgroud)
我在我的博客和最近的MSDN文章中描述了这种死锁行为.
await默认情况下会将其继续计划在当前内部运行SynchronizationContext,或者(如果没有SynchronizationContext)当前运行TaskScheduler.(在这种情况下是ASP.NET请求SynchronizationContext).SynchronizationContext表示请求上下文,ASP.NET一次只允许该上下文中的一个线程.因此,当HTTP请求完成时,它会尝试进入SynchronizationContext运行状态InfoFormat.但是,已经存在一个线程SynchronizationContext- 阻塞的线程Result(等待async方法完成).
另一方面,默认情况下,默认行为ContinueWith会将其继续计划为当前TaskScheduler(在本例中为线程池TaskScheduler).
正如其他人所指出的那样,最好使用await"一路",即不要阻止async代码.不幸的是,在这种情况下,这不是一个选项,因为MVC不支持异步操作过滤器(作为旁注,请在此投票支持此支持).
因此,您的选择是使用ConfigureAwait(false)或仅使用同步方法.在这种情况下,我建议使用同步方法.ConfigureAwait(false)只有当Task它应用于尚未完成时才有效,所以我建议你在使用之后ConfigureAwait(false),应该await在该点之后的方法中使用它(在这种情况下,在调用堆栈中的每个方法中).如果ConfigureAwait(false)由于效率原因而被使用,则那很好(因为它在技术上是可选的).在这种情况下,ConfigureAwait(false)出于正确原因需要,因此IMO会产生维护负担.同步方法会更清晰.
Your first line:
var user = _authService.GetUserAsync(username).Result;
Run Code Online (Sandbox Code Playgroud)
blocks that thread and the current context while it waits for the result of GetUserAsync.
When using await it attempts to run any remaining statements back on the original context after the task being waited on finishes, which causes deadlocks if the original context is blocked (which is is because of the .Result). It looks like you attempted to preempt this problem by using .ConfigureAwait(false) in GetUserAsync, however by the time that that await is in effect it's too late because another await is encountered first. The actual execution path looks like this:
_authService.GetUserAsync(username)
_httpClientWrp.GetStringAsync(url) // not actually awaiting yet (it needs a result before it can be awaited)
await _client.GetStringAsync(url) // here's the first await that takes effect
Run Code Online (Sandbox Code Playgroud)
When _client.GetStringAsync finishes, the rest of the code can't continue on the original context because that context is blocked.
ContinueWith doesn't try to run the other block on the original context (unless you tell it to with an additional parameter) and thus does not suffer from this problem.
这是您注意到的行为差异.
如果你还想用async而不是ContinueWith,你可以添加.ConfigureAwait(false)到遇到的第一个async:
string result = await _client.GetStringAsync(url).ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)
您可能已经知道的,它告诉您await不要尝试在原始上下文中运行剩余的代码.
尽可能尝试在使用async/await时不使用阻塞方法.请参阅调用异步方法时防止死锁,而不使用await以避免将来出现此问题.
| 归档时间: |
|
| 查看次数: |
6112 次 |
| 最近记录: |