没有通过 GetAwaiter().GetResult()

The*_*yer 2 c# .net-4.5 asp.net-4.5

我的代码内部有一个 PostAsync 方法,它似乎永远不会返回响应。但是,我通过 同步使用它.GetAwaiter().GetResult()。目标框架是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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我所做的是在另一个方法中调用 PostAsync:

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;
}
Run Code Online (Sandbox Code Playgroud)

然后,最后在流程的最顶部,我像这样调用 GetBalance 方法:

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

我根本没有在日志中看到日志,而且我似乎没有得到响应或在.GetAwaiter().GetResult(). 遗憾的是,将最后一个代码块切换为异步并且awaitGetBalance()方法对我来说并不是一个真正的选择。

我无法弄清楚为什么即使在使用该.ConfigureAwait(false)方法之后也没有任何改变。

Ste*_*ary 5

您遇到了阻塞异步代码时发生的常见死锁(我的博客中有详细描述)。有多种方法可以解决这个问题,但它们都是黑客技术,而且没有一种方法适用于所有情况。

在你的情况下,我会说要么使用直接阻止 hack要么使用布尔参数 hack

直接阻止黑客需要你到处使用ConfigureAwait(false)。请注意,您当前的代码仅在ConfigureAwait(false)执行任何操作的情况下使用;配置,因此它需要转到s 所在的位置。他们全部。ConfigureAwaitawaitawait

布尔参数 hack 意味着您的代码将采用一个bool参数来确定它是同步执行还是异步执行。请注意HttpClient(目前)有一个仅异步 API,因此您的自定义委托处理程序将需要支持直接阻塞,使用ConfigureAwait(false). 同样,Log要么需要同步等效项,要么也支持直接阻塞。你的代码最终会看起来像这样:

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 }");
Run Code Online (Sandbox Code Playgroud)