HttpClient SendAsync DeadLock

时间:2019-07-08 03:33:25

标签: c#

我有一个创建函数的包装器 创建公共异步任务getCacheToken 一些内部服务/应用程序调用

并且正在另一个服务中调用performExtract()来遇到此异常(请参阅以下内容)。

performExtract实际上是通过API调用来调用getCacheToken

我不禁在sync方法(旧版环境)中发送异步调用,因此每次我在循环中调用var results = client.SendAsync(requestData).Result'时,都会导致死锁, 如果我正确理解它,则在send循环内部使用sendAsync,它将在启动另一个任务之前等待任务完成,因此它不应有以下异常(已取消连接?)

要解决此问题,我必须重写send Async ConfigureAwait(false)并解决了我的问题。

我的问题是如何添加ConfigureAwait(false)来解决问题?

为避免此问题,可以使用带有错误参数的名为ConfigureAwait的方法。当您这样做时,这告诉任务可以在任何可用线程上恢复自身,而不用等待最初创建它的线程。这样可以加快响应速度并避免许多死锁。

以及如何异步运行它导致死锁?

非常感谢您的所有患者通过帖子阅读。

 protected override ExtractResultStatus PerformExtract()
        {
            //EngageRestClient client = new EngageRestClient(_apiEndPoint);
            //client.Authenticator = new NtlmAuthenticator();
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
            try
            {
                var numErrors = 0;

                var dt = GetInfos();
                var fileName = string.Format(FilenameBase, DateTime.Now);
                if (dt.Rows.Count > 0)
                {
                    dt.Columns.Add("failureReason");
                    foreach (DataRow row in dt.Rows)
                    {
                        var referenceID = row["U3l_ReferenceId"].ToString();
                        var requestData = new HttpRequestMessage
                        {
                            Method = HttpMethod.Get,
                            RequestUri = new Uri(_apiEndPoint + $"?referenceID={referenceID}"),
                        };

                        requestData.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
                        var results = client.SendAsync(requestData).Result;
                        var resultResponse = results.Content.ReadAsStringAsync().Result;








    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        HttpResponseMessage response = null;
        for (int i = 0; i < MaxRetries; i++)
        {
            response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
            if (response.StatusCode != HttpStatusCode.InternalServerError ||
                response.StatusCode != HttpStatusCode.NotImplemented ||
                response.StatusCode != HttpStatusCode.GatewayTimeout ||
                response.StatusCode != HttpStatusCode.ServiceUnavailable)
            {
                return response;
            }

            response.Dispose();
        }

        return response;
    }



public async Task<string> GetCacheToken()
        {
            ObjectCache cache = MemoryCache.Default;
            string refreshToken = cache.Get("refreshToken", null) == null ? GetToken() : cache.Get("refreshToken", null).ToString();

            if (!cache.Contains("apiToken"))
            {
                var httpContent = new StringContent("", Encoding.UTF8, "application/x-www-form-urlencoded");
                var dict = new Dictionary<string, string>();
                dict.Add("grant_type", "refresh_token");
                dict.Add("refresh_token", refreshToken);
                var requestData = new HttpRequestMessage
                {
                    Method = HttpMethod.Post,
                    RequestUri = new Uri("https://oauth2.sky.blackbaud.com/token"),
                    Content = new FormUrlEncodedContent(dict)
                };

                requestData.Headers.Authorization = new AuthenticationHeaderValue("Basic", Settings.BasicAuth);
                var results = await _client.SendAsync(requestData);
                var resultResponse = results.Content.ReadAsStringAsync().Result;

                try
                {
                    results.EnsureSuccessStatusCode();
                    var result = _js.Deserialize<TokenModel>(resultResponse);
                    //token expires in one hour from blackbaud
                    var expiration = DateTimeOffset.UtcNow.AddMinutes(55);
                    cache.Add("apiToken", result.access_token, expiration);
                    cache.Add("refreshToken", result.refresh_token, expiration);
                    UpdateToken(result.access_token, result.refresh_token);
                }
                catch (Exception e)
                {
                    var exceptionMessage = $"ResultMessage : {resultResponse} Exception: {e}. Message: {e.Message}. Stacktrace {e.StackTrace}";
                    Log.Exception(e,exceptionMessage);
                    throw;
                }
            }

            return cache.Get("apiToken", null).ToString();
        }
  

{数据:[],HResult:-2146233088,HelpLink:null,InnerException:null,   消息:“响应状态代码不表示成功:400(错误   请求)。,来源:“ System.Net.Http”,StackTrace:“位于   System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\ r \ n位于   RaisersEdge.Infrastructure.Cache.d__2.MoveNext()\ r \ n ---   从先前引发异常的位置开始的堆栈跟踪结束   --- \ r \ n在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\ r \ n
  在   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)\ r \ n   RaisersEdge.Controllers.BaseController.d__6.MoveNext()\ r \ n ---   从先前引发异常的位置开始的堆栈跟踪结束   --- \ r \ n在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\ r \ n
  在   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)\ r \ n   System.Threading.Tasks.TaskHelpersExtensions.d__3`1.MoveNext()\ r \ n ---   从先前引发异常的位置开始的堆栈跟踪结束   --- \ r \ n在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\ r \ n
  在   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)\ r \ n   System.Web.Http.Controllers.ApiControllerActionInvoker.d__0.MoveNext()\ r \ n ---   从先前引发异常的位置开始的堆栈跟踪结束   --- \ r \ n在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\ r \ n
  在   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)\ r \ n   System.Web.Http.Controllers.ActionFilterResult.d__2.MoveNext()\ r \ n ---   从先前引发异常的位置开始的堆栈跟踪结束   --- \ r \ n在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\ r \ n
  在   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)\ r \ n   System.Web.Http.Dispatcher.HttpControllerDispatcher.d__1.MoveNext()“,   TargetSite:“ System.Net.Http.HttpResponseMessage   sureSuccessStatusCode()“,_ typeTag:” HttpRequestException“}

2 个答案:

答案 0 :(得分:1)

正如其他人所提到的,您应该不要使用.Result,因为它是evil!但是如果您使用的是旧版应用,则可以使用以下解决方法:

using System.Threading.Tasks;

public class AsyncHelper
{
        private static readonly TaskFactory _taskFactory = new
            TaskFactory(CancellationToken.None,
                        TaskCreationOptions.None,
                        TaskContinuationOptions.None,
                        TaskScheduler.Default);

        public static TReturn RunSync<TReturn>(Func<Task<TReturn>> task)
        {
            return  _taskFactory.StartNew(task)
                                .Unwrap()
                                .GetAwaiter()
                                .GetResult();
        }
}

然后,您可以使用帮助程序轻松调用方法:

var results = AsyncHelper.RunSync<System.Net.Http.HttpResponseMessage>( 
 () => client.SendAsync(requestData)
);

helper类创建,配置并运行一个异步任务,然后对其进行解包并同步等待以获取结果:这几乎是await所做的,这种方法将防止死锁,可以在try / catch块中使用

当然,调用async方法的唯一正确方法是使用await,但是当您无法在sync方法中调用async方法时,此解决方法是更好的选择。

答案 1 :(得分:1)

  

如何异步运行它会导致死锁?

await by default captures a context and resumes executing the async method in that context。如果此上下文一次只允许一个线程,并且调用代码通过调用ResultWait然后是the code causes a deadlock since the async method cannot resume(和因此无法完成。

  

如何添加ConfigureAwait(false)解决问题?

因为await不再捕获其上下文。 async方法可以在任何线程池线程上恢复,并且不受上下文中阻塞的线程的影响。 ConfigureAwait(false) is generally considered a best practice for library code

  

其中一项服务位于旧版环境中,因此无法调用等待,因为我没有异步函数可以覆盖

有一个variety of hacks you can use to attempt to block on asynchronous code safely。他们都不在任何情况下都能工作。如果ConfigureAwait(false)为您工作,那我会用。