使用ContinueWith或异步等待时不同的行为 [英] Different behavior when using ContinueWith or Async-Await

查看:238
本文介绍了使用ContinueWith或异步等待时不同的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我在电话的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文章我的博客和

因此​​,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 current SynchronizationContext, or (if there is no SynchronizationContext) the current TaskScheduler. (Which in this case is the ASP.NET request SynchronizationContext).
  • 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆