Sanjay Karia Sanjay Karia - 1 month ago 57
HTTP Question

How to use HttpClient.PostAsync asynchronous when the URL responds after an artificial timeout?

How can HttpClient.PostAsync be used to post HTTP requests to URLs which have an artificial time for sending back a response.

Please note the URL and the parameter sleep=30 which is used to introduce an artificial delay of 30 seconds before sending back an HTTP response.

Console.WriteLine("Start time : " + DateTime.Now);

for (int i = 1; i <= 10; i++)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(@"http://fake-response.appspot.com/api/?data={Hello World}&sleep=30");
//client.BaseAddress = new Uri(@"http://fake-response.appspot.com/api/?data={Hello World}&status=200");

client.Timeout = new TimeSpan(0, 0, 60);

var parameters = new Dictionary<string, string>();
parameters["ContentType"] = "text/plain;charset=UTF-8";

//Create Task to POST to the URL
client.DefaultRequestHeaders.ExpectContinue = true;
var response = await client.PostAsync(client.BaseAddress, new FormUrlEncodedContent(parameters));

Task<bool> b1 = ProcessURLAsync(response, 1, 5, 2);
}
}

Console.WriteLine("End time : " + DateTime.Now);


What needs to be done is that async HTTP Posts need to be made in a loop and should not be dependent on the timeout specified in the URL.
However, the PostAsync times out before a response is received.

Please check the time needed for POSTing the 2 different URLs in a loop of 10 async POSTs

I checked HttpClient.DefaultRequestHeaders.ExpectContinue , but I do not think this might help in this use case.

Evk Evk
Answer

That artificial delay is no different from network timeout, from client's perspective. So you should set client.Timeout to the maximum expected artificial delay + real network timeout time. If you don't want to block waiting for response - just not await Task returned from PostAsync. You can store all such tasks in some list and wait them all to complete with await Task.WhenAll(yourTaskList). Or you can use ContinueWith to perform specific actions when given task will be completed. However, if you care about response at all - you have to set large enough timeout anyway, otherwise request will be aborted prematurely.

Here is some sample code to help you out

    static async void MakeRequests()
    {
        var requests = new List<Task<bool>>();
        for (int i = 1; i <= 10; i++)
        {
            // no await here, so, not await MakeRequest(i);
            requests.Add(MakeRequest(i));
        }
        // now all 10 requests are running in parallel
        try {
            await Task.WhenAll(requests);
        }
        catch {
           // no need to handle it here - we handle all errors below 
        }

        // if we are here, all requests are either completed or failed, inspect their results
        foreach (var request in requests) {
            if (request.IsCanceled) {
                // failed by timeout
            }
            else if (request.IsFaulted) {
                // failed
                Log(request.Exception);
            }
            else {
                // success
                bool result = request.Result;
                // handle your result here if needed
            }
        }
    }

    static async Task<bool> MakeRequest(int i) {
        using (var client = new HttpClient()) {
            client.BaseAddress = new Uri(@"http://fake-response.appspot.com/api/?data={Hello World}&sleep=30");
            //client.BaseAddress = new Uri(@"http://fake-response.appspot.com/api/?data={Hello World}&status=200");
            // no timeout here, or set to max expected delay
            //client.Timeout = new TimeSpan(0, 0, 60);

            var parameters = new Dictionary<string, string>();
            parameters["ContentType"] = "text/plain;charset=UTF-8";

            //Create Task to POST to the URL
            client.DefaultRequestHeaders.ExpectContinue = true;
            var response = await client.PostAsync(client.BaseAddress, new FormUrlEncodedContent(parameters));

            Task<bool> b1 = ProcessURLAsync(response, 1, 5, 2);
            return b1;
        }
    }