为啥 HttpClient 似乎在没有 .Result 的情况下挂起? [复制]

Posted

技术标签:

【中文标题】为啥 HttpClient 似乎在没有 .Result 的情况下挂起? [复制]【英文标题】:Why does HttpClient appear to hang without .Result? [duplicate]为什么 HttpClient 似乎在没有 .Result 的情况下挂起? [复制] 【发布时间】:2019-05-31 12:30:39 【问题描述】:

我有这段代码来调用返回令牌的 API。但是,只有当我替换此行时它才会返回:

var response = await TokenClient.PostAsync(EnvironmentHelper.TokenUrl, formContent);

用这一行:

var response = TokenClient.PostAsync(EnvironmentHelper.TokenUrl, formContent).Result;

为什么?

public static async Task<Token> GetAPIToken()

    if (DateTime.Now < Token.Expiry)
        return Token;

    TokenClient.DefaultRequestHeaders.Clear();
    TokenClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    TokenClient.BaseAddress = new Uri(EnvironmentHelper.BaseUrl);
    TokenClient.Timeout = TimeSpan.FromSeconds(3);

    var formContent = new FormUrlEncodedContent(new[]
    
        new KeyValuePair<string, string>("grant_type", "client_credentials"),
        new KeyValuePair<string, string>("client_id", EnvironmentHelper.APITokenClientId),
        new KeyValuePair<string, string>("client_secret", EnvironmentHelper.APITokenClientSecret)
    );

    try
    
        // HANGS - this next line will only ever return if suffixed with '.Result'
        var response = await TokenClient.PostAsync(EnvironmentHelper.TokenUrl, formContent);

        var content = await response.Content.ReadAsStringAsync();
        dynamic jsonContent = JsonConvert.DeserializeObject(content);

        Token token = new Token();
        token.AccessToken = jsonContent.access_token;
        token.Type = jsonContent.token_type;
        token.Expiry = DateTimeOffset.Now.AddSeconds(Convert.ToDouble(jsonContent.expires_in.ToString()) - 30);
        token.Scope = Convert.ToString(jsonContent.scope).Split(' ');

        Token = token;
    
    catch (Exception ex)
    
        var m = ex.Message;
    

    return Token;

【问题讨论】:

你怎么打电话给GetAPIToken?你是 awaiting 一路向上吗? @DavidG - 差不多。不执行等待的最顶层代码如下: var wm = new WebRepo().GetWeb(Id).Result; 是的,不要那样做。一直使用await,否则您将面临像您所看到的那样陷入僵局的危险。 @MattW 真正的解决方案是删除所有.Result.Wait() 调用,使用async/await 一直到顶部。另一种选择是使用ConfigureAwait(false),这意味着您的代码不会返回到 UI 线程。这意味着您也无法修改 UI 根据函数的使用情况,您的程序肯定会陷入死锁。如果调用代码不是异步的,则将其包装在一个任务中并等待方法调用并删除 .Result 【参考方案1】:

不执行等待的最顶层代码如下:var wm = new WebRepo().GetWeb(Id).Result

您遇到了deadlock since the synchronous code is blocking on async code。最好的解决方案是remove the blocking and go async all the way。

调用代码是一个带有 getter 的公共静态属性,在 100 多个地方引用。我不确定如何最好地将属性转换为异步友好

有a few approaches to async properties,取决于属性的语义是什么。如果每次调用都重新计算,那它真的是变相的方法,应该变成async Task&lt;T&gt;的方法。如果设置一次,那么AsyncLazy&lt;T&gt;可能是更好的选择。

有没有办法包装其中一个电话?去重构这么多代码是一项艰巨的任务。

没有适用于所有场景的包装。但是,有一些techniques for mixing sync and async code 可以适用于特定场景。

以下是我要考虑的,按优先顺序排列:

    让它一直异步。重构代码并使其变得更好。 (需要大量代码更改,目前可能不可行)。 让它一直同步。将GetAPIToken(及其调用者)更改为同步而不是异步。 (要求所有调用代码同步,这可能是不可能的)。 使用boolean argument hack 使GetAPIToken 可以同步和异步调用。 (需要能够更改GetAPIToken)。 使用thread pool hack 在线程池线程上异步运行GetAPIToken 并同步阻塞它上面的另一个线程。

【讨论】:

看起来真正的挑战是唯一真正的保证是让等待的整个应用程序与遗留代码的性质发生冲突。

以上是关于为啥 HttpClient 似乎在没有 .Result 的情况下挂起? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥没有将详细的错误消息传递给 HttpClient?

Android SDK使用HttpClient的问题,为啥老是报错,困扰了我好几天,求大神解救!

为啥在 WebApi 上下文中的 using 块中使用 HttpClient 是错误的?

在 Angular 7 中,为啥即使只订阅一次 HttpClient.post 也会执行两次?

为啥 HttpClient 不保存基地址,即使它在 Startup 中设置

为啥 C# HttpClient 不能调用这个 URL(总是超时)?