Not getting past GetAwaiter().GetResult()

2.9k Views Asked by At

I have a PostAsync method in an internal part of my code that doesn't seem to ever return a response. However, I use it synchronously via .GetAwaiter().GetResult(). The target framework is net45.

public async Task<TResponse> PostAsync<TResponse, TRequest>(string method, TRequest body)
{
    _logger.Log($"Method {method}, body {JsonConvert.SerializeObject(body)} on url {_configuration.ApiUrl}");
    using (var customDelegatingHandler = new HMACDelegatingHandler(_configuration, _apiId))
    {
        using (var client = new HttpClient(customDelegatingHandler))
        {
            var response = await client.PostAsync($"{_configuration.ApiUrl}/{method}",
                new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"));

            if (response.StatusCode == HttpStatusCode.OK)
            {
                var content = await response.Content.ReadAsStringAsync();

                return JsonConvert.DeserializeObject<TResponse>(content);
            }
            else
            {
                await Log(body, response);
            }
            return default;
        }
    }
}

What I do is call the PostAsync in another method:

public async Task<decimal> GetBalance(Request request)
{
    // = new MyCustomClient...

    QueryFundsResponse response = await customClient.PostAsync<Response, Request>("testAction", request);
    if (response == default)
        return 0.0m;

    return response.Amount;
}

Then, finally at the very top of the flow, I call the GetBalance method like this:

var sw = new Stopwatch();
sw.Start();
var balance = _provider
    .GetBalance(request)
    .ConfigureAwait(false)
    .GetAwaiter()
    .GetResult();
sw.Stop();
_logger.Log($"GetBalance -> method duration: { sw.ElapsedMilliseconds }");

I don't see the log in my logs at all, and I don't seem to ever get a response or any code executed after the .GetAwaiter().GetResult(). Switching the last block of code to be asynchronous and await the GetBalance() method is not really an option for me, sadly.

I am unable to figure out why nothing is changing, even after using the .ConfigureAwait(false) method.

1

There are 1 best solutions below

0
Stephen Cleary On BEST ANSWER

You're experiencing the common deadlock that happens when you block on asynchronous code (described in detail on my blog). There are a variety of ways to get around it, but they're all hacks and none of them work in every situation.

In your case, I'd say either use the direct blocking hack or use the boolean argument hack.

The direct blocking hack requires you to use ConfigureAwait(false) everywhere. Note that your current code only uses ConfigureAwait(false) where it doesn't do anything; ConfigureAwait configures the await, so it needs to go where the awaits are. All of them.

The boolean argument hack means that your code will take a bool parameter that determines whether it executes synchronously or asynchronously. Note that HttpClient (for now) has an async-only API, so your custom delegating handler will need to support direct blocking, using ConfigureAwait(false). Similarly, Log will either need a synchronous equivalent or also support direct blocking. Your code would end up looking something like this:

public Task<TResponse> PostAsync<TResponse, TRequest>(string method, TRequest body) => PostCoreAsync(method, body, sync: false);
public TResponse Post<TResponse, TRequest>(string method, TRequest body) => PostCoreAsync(method, body, sync: true).GetAwaiter().GetResult();
private async Task<TResponse> PostCoreAsync<TResponse, TRequest>(string method, TRequest body, bool sync)
{
  _logger.Log($"Method {method}, body {JsonConvert.SerializeObject(body)} on url {_configuration.ApiUrl}");
  using (var customDelegatingHandler = new HMACDelegatingHandler(_configuration, _apiId))
  {
    using (var client = new HttpClient(customDelegatingHandler))
    {
      var responseTask = client.PostAsync($"{_configuration.ApiUrl}/{method}",
         new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"));
      var response = sync ? responseTask.GetAwaiter().GetResult() : await responseTask;

      if (response.StatusCode == HttpStatusCode.OK)
      {
        var content = sync ? response.Content.ReadAsStringAsync().GetAwaiter().GetResult() : await response.Content.ReadAsStringAsync();

        return JsonConvert.DeserializeObject<TResponse>(content);
      }
      else
      {
        var logTask = Log(body, response);
        if (sync)
          logTask.GetAwaiter().GetResult();
        else
          await logTask;
      }
      return default;
    }
  }
}

public Task<decimal> GetBalanceAsync(Request request) => GetBalanceCoreAsync(request, sync: false);
public decimal GetBalance(Request request) => GetBalanceCoreAsync(request, sync: true).GetAwaiter().GetResult();
private async Task<decimal> GetBalanceCoreAsync(Request request, bool sync)
{
    // = new MyCustomClient...

    QueryFundsResponse response = sync ?
        customClient.Post<Response, Request>("testAction", request) :
        await customClient.PostAsync<Response, Request>("testAction", request);
    if (response == default)
        return 0.0m;

    return response.Amount;
}

var sw = new Stopwatch();
sw.Start();
var balance = _provider
    .GetBalance(request);
sw.Stop();
_logger.Log($"GetBalance -> method duration: { sw.ElapsedMilliseconds }");