使用ContinueWith或异步等待时不同的行为

编程入门 行业动态 更新时间:2024-10-24 18:16:11
本文介绍了使用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文章我的博客和

  • 的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 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或异步等待时不同的行为

本文发布于:2023-11-10 02:13:25,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1574055.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:ContinueWith

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!