这些 HttpClient 调用是否并行调用?

Does these HttpClient call be called in parallel?

我有一个 .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 应用程序中的 Test() 被并行调用 10 次,那么对远程服务器的 POST 请求是并行调用还是串行调用?

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

事实上,当我有大量的请求列表时,我经常收到消息A connection attempt failed because the connected party didn't properly respond after a period time, or established connection failed because connected主机未能响应

Question is: if Test() in my Web App is called 10 times, in parallel, do the POST requests to the remote server are be called in parallel or in serial?

它们将被并行调用。代码中的任何内容都不会阻止它。

In fact, when I have huge list of requests, I often receive the message A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.

这里至少有两件事需要解决:

  • 正确使用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);
}

然后就是删除 class 中的所有 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
}