Does these HttpClient call be called in parallel?

1.7k views Asked by At

I've a .NET Core web app that, once a web method (i.e. Test()) is invoked, call another remote api.

Basically, I do it this way (here's a POST example, called within a web method 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;
}

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?

Because if they will be called in serial, the last one will take the time of the previous ones, and that's not what I want.

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.

2

There are 2 answers

0
Camilo Terevinto On BEST ANSWER

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?

They will be called in parallel. Nothing in the code would make it in a blocking way.

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.

There are at least 2 things that need to be fixed here:

  • Properly using async/await
  • Properly using HttpClient

The first one is easy:

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
}

The second one is a bit more tricky and can quite possibly be the cause of the problems you're seeing. Generally, unless you have some very specific scenario, new HttpClient() is plain wrong. It wastes resources, hides dependencies and prevents mocking. The correct way to do this is by using IHttpClientFactory, which can be very easily abstracted by using the AddHttpClient extension methods when registering the service.

Basically, this would look like this:

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

Then it's a matter of getting rid of all using HttpClient in the class and just:

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
}
0
Anoop Iype On

[HttpClient call using parallel and solve JSON Parse error]

You can use task. when for parallel call . but when you use N number of calls and try to parse as JSON this may throw an error because result concatenation change the Json format so we can use Jarray. merge for combining the result .


Code

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

  using(HttpClient client = new HttpClient()) {

    string responseContent = string.Empty;
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
    string json = JsonConvert.SerializeObject(entity);
    var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
    var tasks = new List < Task < string >> ();

    var jArrayResponse = new JArray();

    int count = 10; // number of call required
    for (int i = 0; i <= count; i++) {
      async Task < string > RemoteCall() {
        var response = await client.PostAsync($"{baseAddress}/{url}", body);
        return await response.Content.ReadAsStringAsync();
      }

      tasks.Add(RemoteCall());
    }

    await Task.WhenAll(tasks);
    foreach(var task in tasks) {
      string postResponse = await task;

      if (postResponse != "[]") {
        var userJArray = JArray.Parse(postResponse);
        jArrayResponse.Merge(userJArray);

      }

    }
    responseContent = jArrayResponse.ToString();

    return responseContent;

  }

}