【问题标题】:HttpClient in async/sync implementation returns WaitingForActivation异步/同步实现中的 HttpClient 返回 WaitingForActivation
【发布时间】:2019-08-12 13:05:42
【问题描述】:

我在 HttpClient 的异步同步实现方面遇到问题。

Id = 8, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"

我知道我正在做的可能是一种不好的做法,最好让所有路径异步,但这是公司向我提出的要求,所以我必须这样做。

项目是在 NET Standard 1.1 中构建的,可用作 NuGet 包并与 Framework 和 Core 兼容。

这是我的主要客户端构造...

private static HttpClient _client;
private static Uri _baseAddress;
private static readonly JsonSerializerSettings _settings = new JsonSerializerSettings
    { DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, MissingMemberHandling = MissingMemberHandling.Ignore };

public Client() { }

private Client(string baseUrl, Config config)
{
    _baseAddress = new Uri(baseUrl);

    _client = new HttpClient { Timeout = TimeSpan.FromSeconds(config.Timeout) };

    _client.DefaultRequestHeaders.Accept.Clear();
    _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    _client.DefaultRequestHeaders.Add("X-API-KEY", config.Token);
}

private Client _paymentClient;
private Client _mainClient;

public Client Create(bool payment, Config config = null)
{
    if (!payment)
    {
        _mainClient = _mainClient ?? new Client("https://api.address.com/", config);
        return _mainClient;
    }

    _paymentClient = _paymentClient ?? new Client("https://payment.address.com/", config);
    return _paymentClient;
}

public void Dispose() => _client.Dispose();

private static async Task<T> Send<T>(HttpMethod method, string url, object data = null)
{
    var uri = new UriBuilder(_baseAddress);
        uri.Path += url;

    var request = new HttpRequestMessage(method, uri.Uri);

    if (data != null)
        request.Content = new StringContent(JsonConvert.SerializeObject(data, _settings), Encoding.UTF8, "application/json");

    var response = await _client.SendAsync(request).ConfigureAwait(false);
    response.EnsureSuccessStatusCode();

    var content = await response.Content.ReadAsStringAsync();

    T result = default;

        if (response.IsSuccessStatusCode)
        {
            if (response.Content.Headers.ContentType.MediaType == "application/json")
            {
                var responseObj = JsonConvert.DeserializeObject<Response<T>>(content, _settings);

                if (responseObj.HasError)
                    throw new Safe2PayException(responseObj.ErrorCode, responseObj.Error);

                responseObj.ResponseDetail = result;
            }
        }
        else throw new Exception((int) response.StatusCode + "-" + response.StatusCode);

    request.Dispose();
    response.Dispose();

    return result;
}

Send&lt;T&gt; 方法应该是处理请求和响应的一般处理方法,包装在这样的通用调用中:

internal Task<T> Get<T>(string url) => Send<T>(HttpMethod.Get, url);

//OR even async...

internal async Task<T> Get<T>(string url) => await Send<T>(HttpMethod.Get, url);

这样调用,发送和接收数据..

private Client Client { get; }

public CheckoutRequest(Config config) => Client = new Client().Create(true, config);

public object Credit(Transaction transaction)
{
    var response = Client.Post<Transaction>("v2/Payment", transaction);
    return response;
}

我的问题是客户总是给我一个WaitingfForActivation 甚至是RunningWaitingToRun,如果我把它改成...

Task.Run(() => Send<T>(HttpMethod.Get, url));
//or
Task.Run(() => Send<T>(HttpMethod.Get, url).Result);
//or
Task.Run(async () => await Send<T>(HttpMethod.Get, url));
//or
Task.Run(async () => await Send<T>(HttpMethod.Get, url).ConfigureAwait(false));

我一直试图找出我做错了什么,试图改变所有的等待,但我没有成功,所以非常感谢任何帮助。

【问题讨论】:

  • 你为什么有.ConfigureAwait(false);
  • @AzharKhorasany 它对任务完成没有任何影响,但是在客户端调用中使用它至少可以进入 WaitingForActivation。没有它,它会返回 Running 甚至 WaitingToRun 状态。我是这方面的新手,所以我正在尝试查看此实现的示例,但我没有找到太多关于同步/异步用法的信息......
  • 使用 ConfigureAwait(false) 来避免死锁是一种危险的做法。您必须对阻塞代码调用的所有方法的传递闭包中的每个等待使用 ConfigureAwait(false),包括所有第三方和第二方代码。使用 ConfigureAwait(false) 避免死锁充其量只是一种技巧。
  • 在这一行: var response = Client.Post("v2/Payment", transaction);后面的调用是如何执行的?是在等待 Send(Method.Post, ...) 吗?为什么您的 Client.Post 不是 async/await?
  • @AzharKhorasany 我从方法中删除了所有 ConfigureAwait 并将 POST 调用更改为 async/await,但返回相同的 WaitingForActivation。必须同步调用该方法的最终用法,所以这是我的主要问题。如果我将其全部转为 async/await,则方法可以工作并提供预期的结果。有什么方法可以让它在被调用时双向工作?

标签: c# async-await dotnet-httpclient


【解决方案1】:

我怀疑你的问题在这里:

public object Credit(Transaction transaction)
{
    var response = Client.Post<Transaction>("v2/Payment", transaction);
    return response;
}

您没有显示Post&lt;T&gt;() 的代码,但我认为它也是async Task&lt;T&gt; 方法,这意味着responseTask&lt;T&gt;,您的代码基本上是这样做的:

  1. 开始一项任务。
  2. 返回未完成任务的描述。

当我认为这确实是你想要的:

  1. 开始任务。
  2. 等待任务完成。
  3. 返回任务结果。

理想情况下,这应该是一个async 方法,您可以await 任务:

public async Task<object> Credit(Transaction transaction)
{
    var response = await Client.Post<Transaction>("v2/Payment", transaction);
    return response;
}

如果您绝对必须同步等待任务(需要这样做的理由很少),那么您可以使用.GetAwaiter().GetResult()

public object Credit(Transaction transaction)
{
    var response = Client.Post<Transaction>("v2/Payment", transaction).GetAwaiter().GetResult();
    return response;
}

.GetAwaiter().GetResult() 而不是.Result 的主要好处是,在出现异常的情况下,它将抛出实际 异常而不是AggregateException

另外,你可以让你的Create()方法static

public static Client Create(bool payment, Config config = null)

那么你不需要初始化类来调用它:

public CheckoutRequest(Config config) => Client = Client.Create(true, config);

更新:如果您想要 async 和非异步版本的相同方法,您可以遵循 Microsoft 使用的相同标准,并使用 Async 后缀命名 async 方法.非异步版本只能调用异步版本。例如:

public async Task<object> CreditAsync(Transaction transaction)
{
    var response = await Client.Post<Transaction>("v2/Payment", transaction);
    return response;
}

public object Credit(Transaction transaction)
{
    return CreditAsync(transaction).GetAwaiter().GetResult();
}

【讨论】:

  • 使用.GetAwaiter().GetResult() 解决了这个问题!非常感谢!如果它的要求不高,我想保持Credit() 实现异步是可等待的,但是有什么方法可以改变它来调用它同步或异步?我的意思是就像Credit(transaction)await Credit(transaction) 一样不需要太多修改或新方法?再次感谢。
  • 不,您不能,因为 C# 不允许您(有充分理由)拥有两个具有相同名称和相同参数但返回不同类型的方法。但是你可以有一个名为CreditAsync()async 方法,然后让你的非异步Credit() 方法调用CreditAsync().GetAwaiter().GetResult()
  • 哦,我明白了。使用 until 这个实现的错误代码,直到 Post 是异步的,并且例如包含在同步 Credit 和异步 CreditAsync 中?再次感谢。
  • 我不明白你在问什么,抱歉。但是我用同一​​方法的异步和非异步版本的示例更新了我的答案。
  • @Felipe - .GetAwaiter().GetResult() 在图书馆中绝对应该避免使用。
猜你喜欢
  • 1970-01-01
  • 2020-08-04
  • 2018-04-08
  • 2017-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多