为什么HttpClient似乎在没有.Result的情况下挂起?

时间:2019-05-31 12:30:39

标签: c# async-await httpclient .net-4.6.1

我有这段代码来调用返回令牌的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;
}

1 个答案:

答案 0 :(得分:0)

  

未执行等待的最上面的代码是这样的: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<T>方法。如果设置一次,那么AsyncLazy<T>可能是更好的选择。

  

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

没有适用于所有情况的包装。但是,有些techniques for mixing sync and async code可以在特定情况下使用。

以下是我考虑的顺序:

  1. 使其始终保持异步状态。重构代码并使之更好。 (需要更改很多代码,目前可能不可行。)
  2. 使其始终保持同步。将GetAPIToken(及其调用者)更改为同步而不是异步。 (要求所有调用代码都同步,这可能是不可能的。)
  3. 使用boolean argument hack使GetAPIToken可以同步和异步地被调用。 (需要具有更改GetAPIToken的能力。)
  4. 使用thread pool hack在线程池线程上异步运行GetAPIToken并同步阻止该线程上的另一个线程。