【问题标题】:HttpClient SendAsync DeadLockHttpClient SendAsync 死锁
【发布时间】:2019-11-17 13:35:28
【问题描述】:

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

我在另一个服务中调用 performExtract() 时遇到了这个异常(请参见下文)。

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

我忍不住在同步方法(旧环境)中发送异步调用,所以每次我在循环中调用 var results = client.SendAsync(requestData).Result' 时都会导致死锁, 如果我理解正确,sendAsync 在 for 循环中,它会在开始另一个任务之前等待一个任务完成,所以它不应该有以下异常(连接已处理?)

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

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

为避免此问题,您可以使用带有 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();
        }

{Data: [], HResult: -2146233088, HelpLink: null, InnerException: null, 消息:“响应状态码不表示成功:400(错误 请求)。”,来源:“System.Net.Http”,StackTrace:“在 System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\r\n at 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()", 目标站点:“System.Net.Http.HttpResponseMessage EnsureSuccessStatusCode()", _typeTag: "HttpRequestException"}

【问题讨论】:

  • 看到这个.Result了吗?不要这样做......永远......除非你确切地知道你在做什么。它可能会导致您正在寻找的死锁。
  • 其中一项服务位于旧环境中,无法调用 await,因为我没有要覆盖的异步函数

标签: c#


【解决方案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)
);

帮助类创建、配置和运行一个异步任务,然后解包并同步等待它得到结果:这几乎就是 await 所做的,这种方法可以防止死锁,并且可以在 try/catch 块中使用.

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

【讨论】:

    【解决方案2】:

    异步运行如何导致死锁?

    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.

    其中一项服务位于旧环境中,因此无法调用 await,因为我没有要覆盖的异步函数

    有一个variety of hacks you can use to attempt to block on asynchronous code safely。它们都不适用于所有情况。如果ConfigureAwait(false) 适合你,那么我会使用它。

    【讨论】:

    • 你能看看其他帖子吗?我有一个在确切时间调用两次的异步方法。不知道我如何能够防止这种情况发生,因为它已经在异步功能中,想要使用锁,但我不能在等待调用中使用它。继承人的链接非常感谢您的帮助。 stackoverflow.com/questions/56947073/…
    猜你喜欢
    • 2020-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多