【问题标题】:Does these HttpClient call be called in parallel?这些 HttpClient 调用是否被并行调用?
【发布时间】:2021-11-04 08:27:58
【问题描述】:

我有一个 .NET Core Web 应用,一旦调用了一个 Web 方法(即 Test()),就会调用另一个远程 api。

基本上,我是这样做的(这是一个 POST 示例,在 Web 方法 Test() 中调用):

public T PostRead<T>(string baseAddress, string url, out bool succeded, object entity = null)
{
    T returnValue = default(T);
    succeded = false;

    try
    {
        using (HttpClient client = new HttpClient())
        {
            client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
            HttpResponseMessage res = null;
            string json = JsonConvert.SerializeObject(entity);
            var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");

            var task = Task.Run(() => client.PostAsync($"{baseAddress}/{url}", body));
            task.Wait();
            res = task.Result;
            succeded = res.IsSuccessStatusCode;

            if (succeded)
            {
                returnValue = JsonConvert.DeserializeObject<T>(res.Content.ReadAsStringAsync().Result);
            }
            else
            {
                Log($"PostRead failed, error: {JsonConvert.SerializeObject(res)}");
            }
        }
    }
    catch (Exception ex)
    {
        Log($"PostRead error: {baseAddress}/{url} entity: {JsonConvert.SerializeObject(entity)}");
    }

    return returnValue;
}

问题是:如果我的 Web App 中的 Test() 被并行调用 10 次,对远程服务器的 POST 请求是并行调用还是串行调用?

因为如果它们会被串行调用,最后一个会占用前一个的时间,这不是我想要的。

事实上,当我有大量的请求时,我经常会收到消息连接尝试失败,因为连接的一方在一段时间后没有正确响应,或者建立连接失败,因为连接的主机失败回应

【问题讨论】:

  • 我能问一下你为什么不完全异步吗?为什么要打扰Task.Run 而不是GetAwaiter().GetResult()?如果你的PostRead 方法是异步的,你可以并行调用PostReadTask.WaitAll。另外,HttpClient 不应该在整个应用程序中真正注入和重用(您只需操作一次 defaultheaders 和 api 键)
  • 请阅读You're (probably still) using HttpClient wrong and it is destabilizing your software。然后:摆脱Task.Run 并正确使用asyncawait,你只是无缘无故地浪费线程,而不是让ASP.NET Core 有效地管理它们。至于实际问题,这里没有lock,所以我不知道是什么让你认为他们会串行运行
  • @Camilo Terevinto 使用它是否正确? stackoverflow.com/a/31102831/365251
  • 否 - 任何时候看到 new HttpClient 都是错误的,除非在最简单的应用程序中每分钟/小时发生一次。 HttpClient 实例需要重用。至于异步部分,是的,这个例子很好
  • 要回答你的问题,除非你在实现中这样做,否则这里没有异步运行。至于您的错误,您需要小心,因为即使以您当前的速度,您也可能会受到限制。我怀疑同时运行的东西会帮助你当前的错误。此外,如前所述,还有更好的方法来使用 httpclient,即 IHttpClientFactory,并让它全部使用async

标签: c# httpclient asp.net-core-2.2


【解决方案1】:

问题是:如果我的 Web App 中的 Test() 被并行调用 10 次,对远程服务器的 POST 请求是并行调用还是串行调用?

它们将被并行调用。代码中的任何内容都不会使其成为阻塞方式。

事实上,当我的请求列表很大时,我经常会收到A connection attempt failed because the connected party after a time没有正确响应,或者是因为已连接的主机没有响应而建立的连接失败。

这里至少有两点需要修复:

  • 正确使用async/await
  • 正确使用HttpClient

第一个很简单:

public async Task<T> PostRead<T>(string baseAddress, string url, out bool succeded, object entity = null)
{
    succeded = false;

    try
    {
        using (HttpClient client = new HttpClient())
        {
            client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
            string json = JsonConvert.SerializeObject(entity);
            var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");

            var responseMessage = await client.PostAsync($"{baseAddress}/{url}", body);
            var responseContent = await responseMessage.Content.ReadAsStringAsync();

            if (responseMessage.IsSuccessStatusCode)
            {
                succeeded = true;
                return JsonConvert.DeserializeObject<T>();
            }
            else
            {
                // log...
            }
        }
    }
    catch (Exception ex)
    {
        // log...
    }

    return default; // or default(T) depending on the c# version
}

第二个有点棘手,很可能是您所看到的问题的原因。通常,除非您有一些非常具体的场景,否则new HttpClient() 是完全错误的。它浪费资源,隐藏依赖关系并防止模拟。正确的做法是使用IHttpClientFactory,在注册服务时使用AddHttpClient扩展方法可以很容易地抽象出来。

基本上是这样的:

services.AddHttpClient<YourClassName>(x => 
{
    x.BaseAddress = new Uri(baseAddress);
    x.DefaultRequestHeaders.Add("Authorization", MyApiKey);
}

然后就是把课堂上的所有using HttpClient 去掉,然后:

private readonly HttpClient _client;

public MyClassName(HttpClient client) { _client = client; }

public async Task<T> PostRead<T>(string url, out bool succeded, object entity = null)
{
    succeded = false;

    try
    {
        string json = JsonConvert.SerializeObject(entity);
        var responseMessage = await _client.PostAsync($"{baseAddress}/{url}", body);
        //...
    }
    catch (Exception ex)
    {
        // log...
    }

    return default; // or default(T) depending on the c# version
}

【讨论】:

    猜你喜欢
    • 2014-08-20
    • 1970-01-01
    • 2011-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多