Search code examples
c#async-awaithttpclient.net-4.6.1

Why does HttpClient appear to hang without .Result?


I have this code to call an API which returns a token. However, it will only return if I replace this line:

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

with this line:

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

Why?

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;
}

Solution

  • The top-most code where awaiting is not performed has this: var wm = new WebRepo().GetWeb(Id).Result

    You're running into a deadlock since the synchronous code is blocking on async code. The best solution is to remove the blocking and go async all the way.

    The calling code is a public static property with a getter, referenced in over 100 places. I'm not sure how best to convert the property to async friendly

    There are a few approaches to async properties, depending on what the property's semantics are. If it re-evaluates each time it's called, then it's really a method in disguise and should become an async Task<T> method. If it is set once, then AsyncLazy<T> may be a better choice.

    Is there a way to wrap one of the calls? It is a mammoth task to go and refactor that much code.

    There's no wrapping that works for all scenarios. However, there are some techniques for mixing sync and async code that can work for specific scenarios.

    Here's what I would consider, in order of precedence:

    1. Make it async all the way. Refactor the code and make it better. (Requires lots of code changes, which may not be feasible at this time).
    2. Make it sync all the way. Change GetAPIToken (and its callers) to be sync instead of async. (Requires all calling code to be made synchronous, which may not be possible).
    3. Use the boolean argument hack to make GetAPIToken callable both synchronously and asynchronously. (Requires the ability to change GetAPIToken).
    4. Use the thread pool hack to run GetAPIToken asynchronously on a thread pool thread and sycnhronously block another thread on it.