Async/Await calls using httpClient with timeout

2019-07-24 16:17发布

问题:

Based on the answer of another question: HttpClient async requests not completing for large batch sent out in a loop

I'm using the extension method and applying the logic they propose to use timeout with HttpClient class and avoid hangs or no response from it.

 public static Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeout)
 {
      var delay = task.ContinueWith(t => t.Result
          , new CancellationTokenSource(timeout).Token);
      return Task.WhenAny(task, delay).Unwrap();
 }

So, calling HttpClient like this should prevent any "Tasks gone bad" from never ending:

  Task<HttpResponseMessage> _response = httpClient.PostAsJsonAsync<Object>(Target, new Object()).WithTimeout<HttpResponseMessage>(httpClient.Timeout);

Now, when applying this, using a Task array populated with a loop, I'd hope that all tasks run in parallel and finish in the specified timeout value. Just for showing a sample, I'm setting the timeout in 400ms, however the next line after the Tasks.WhenAll(tasks) line is hit around 20 seconds after.

public async Task<JObject> GetResponse(JObject request, TimeSpan timeout)
    {   
            Timespan timeout = Timespan.FromMilliseconds(400);  
            HttpClient httpClient = new HttpClient();
            HttpResponseMessage response = await httpClient.PostAsJsonAsync<string>(string.Format("{0}api/GetResponse", endpoint), localRequest.ToString()).WithTimeout<HttpResponseMessage>(timeout);
            return await response.Content.ReadAsAsync<JObject>().WithTimeout<JObject>(timeout); 
    }

Calling the async method:

        Task<JObject>[] tasks = new Task<JObject>[totalResultCounter];

        foreach (JObject request in anArray)
        {
            tasks[counter] = GetResponse(request);
        }

        await Task.WhenAll(tasks);
        MyNextMethod();

Wouldn't it be expected that tasks complete in 500 ms and next line is hit? Why it takes around 20 secs in reach the MyNextMethod line? Maybe I'm doing anything wrong when setting the timeout in PostAsJsonAsync/ReadAsAsync calls?

The timeout extension method works fine isolated.

When I use the keyword await, it seems it waits to complete, however not using the timeout defined:

HttpResponseMessage response = await httpClient.PostAsJsonAsync(string.Format("{0}api/GetSupplierResponse", endpoint), localRequest.ToString()).WithTimeout(timeout);

If I change it to:

 Task<HttpResponseMessage> response = httpClient.PostAsJsonAsync<string>(string.Format("{0}api/GetResponse", endpoint), localRequest.ToString()).WithTimeout<HttpResponseMessage>(timeout);

it is fast.

But then how I get the result? I need to use an await for the ReadAsAsync method:

 JObject result = await response.Content.ReadAsAsync<JObject>().WithTimeout<JObject>(timeout);

Once I use it, it takes a lot of time, 20 secs to complete my loop of around 200 items.

PostAsJsonAsync and ReadAsAsync should be handled as independent tasks that won't respect the timeout specified in httpClient? Or the use of these methods takes more than 500ms and that's why it takes a lot when using a loop?

Any help/conceptual clarification will be appreciated.