I have followed the example here and here to handle timeouts successfully with the C# HttpClient but I just can't make it work!
var urls = new List<string> { "/success", "/willtimeout", "/success" };
var baseAddress = "http://endpoint";
var httpClient = new HttpClient();
httpClient.Timeout = new TimeSpan(0, 0, 30);
httpClient.BaseAddress = new Uri(baseAddress);
foreach (var url in urls)
{
try
{
var cs = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken.None);
cs.CancelAfter(new TimeSpan(0, 0, 3));
var result = await httpClient.GetAsync(urls, cs.Token);
Console.WriteLine("Success");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
This code will print
Success
A task was canceled.
A task was canceled.
How can I make this work properly without creating a new HttpClient every time?
CreateLinkedTokenSource Creates a CancellationTokenSource that will be in the canceled state when any of the source tokens are in the canceled state.
So I guess this is the problem, just create new token source every time and don't link it to anything:
try
{
var cs = new CancellationTokenSource();
// ...
Related
HttpClientHandler httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
HttpClient client = new HttpClient(httpClientHandler);
client.BaseAddress = new Uri(url);
try
{
client.Timeout = TimeSpan.FromSeconds(30);
var response = await client.PostAsync(uri, content);
if (response.IsSuccessStatusCode)
{
// do something
}
}
catch (HttpRequestException ex)
{
// do error stuff
}
catch (TaskCanceledException ex)
{
// do error stuff #2
}
catch (Exception ex)
{
// do error stuff #3
}
I am new to HttpClient.
During our test, we shut down the web service that this code is hitting in order to test this block of code. (BTW, it works as expected when the web service is running.)
Why does it take 2 minutes and 10 sec instead of 30 seconds to hit the TaskCanceledException catch?
The default timeout for HttpClient is 100 seconds. The observed 130 seems pretty strange.
If you run the following code inside dotnet fiddle, then you would see it does cancel the request after 2 seconds and will not wait for a response for 5 seconds:
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class Program
{
public static async Task Main()
{
var handler = new HttpClientHandler();
var client = new HttpClient(handler);
client.BaseAddress = new Uri("https://httpstat.us");
client.Timeout = TimeSpan.FromSeconds(2);
try
{
_ = await client.GetAsync("/200?sleep=5000");
}
catch (OperationCanceledException)
{
"Timed out".Dump();
Environment.Exit(-1);
}
"Finished".Dump();
}
}
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.
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;
}
}
However, the PostAsync times out before a response is received.
This method times out because you set HttpClient.Timeout property to 10 seconds. Setting this property instructs the client to time out after a specified time if response is not received.
How to set Timeout property to Windows.Web.Http.HttpClient operation. The code sample I used is below.
public HttpClient httpClient;
public CancellationTokenSource cts;
public void SendRequest(addressUri,postrequestbody)
{
HttpHelper.CreateHttpClient(ref httpClient);
cts = new CancellationTokenSource();
HttpRequestMessage msg =
new HttpRequestMessage(new HttpMethod("POST"),
new Uri(addressUri));
msg.Content = new HttpStringContent(postrequestbody);
msg.Content.Headers.ContentType =
new HttpMediaTypeHeaderValue("application/json");
HttpResponseMessage response =
await httpClient.SendRequestAsync(msg).AsTask();
if (response.StatusCode == HttpStatusCode.Ok)
{
}
}
Use a CancellationToken:
try
{
CancellationTokenSource cts = new CancellationTokenSource(2000); // 2 seconds
HttpClient client = new HttpClient();
HttpResponseMessage response = await
client.SendRequestAsync(request).AsTask(cts.Token);
}
catch (TaskCanceledException ex)
{
// Catch operation aborted ...
}
I have the following code;
var httpClientHandler = new HttpClientHandler()
{
UseDefaultCredentials = true,
PreAuthenticate = false,
};
var webclient = new HttpClient(httpClientHandler);
webclient.BaseAddress = new Uri("http://localhost");
webclient.Timeout = new TimeSpan(1);
var res = webclient.PostAsync("http://localhost/test", "'test'", new JsonMediaTypeFormatter()).Result; //.ContinueWith(x => ProcessResult(x));
The .Result blocks the thread and hangs the application. If I replace it with the continuation, it is never executed.
If I increase the timeout, the code works, so it seems to be the failing timeout.
I've solved it by using another task to timeout but would like to understand better;
if (await Task.WhenAny(task, Task.Delay(new TimeSpan(0, 0, 0, 20))) == task)
{
//success
}
else
{
//failure
}
I'm trying to call PostAsync method using System.Net.Http.HttpClient from the Web API. I get the following error:
System.AggregateException "A task was canceled."
Task:
Id = 1, Status = System.Threading.Tasks.TaskStatus.Canceled, Method = "{null}", Result = "{Not yet computed}"
Code:
using (HttpClientHandler handler = new HttpClientHandler())
{
handler.Credentials = new NetworkCredential("MyUsername", "p#ssw0rd");
using (HttpClient client = new HttpClient(handler))
{
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("status", "Hello world"));
HttpContent content = new FormUrlEncodedContent(postData);
var responseTask = client.PostAsync(url, content).ContinueWith(
(postTask) =>
{
postTask.Result.EnsureSuccessStatusCode();
});
}
I assume the responseTask will force the method to run synchronously?
It's a WPF application, not ASP.NET.
I was getting this same error and tracked it down to my HttpClient was timing out. The default timeout is 100 seconds. I added the following to the create of the HttpClient.
HttpClient httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromMinutes(10);
In terms of debugging you could try writing an extension method to get the exception:
public static HttpResponseMessage PostAsyncSafe(this HttpClient client, string requestUri, string content)
{
var requestContent = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded");
return PerformActionSafe(() => (client.PostAsync(requestUri, requestContent)).Result);
}
public static HttpResponseMessage PerformActionSafe(Func<HttpResponseMessage> action)
{
try
{
return action();
}
catch (AggregateException aex)
{
Exception firstException = null;
if (aex.InnerExceptions != null && aex.InnerExceptions.Any())
{
firstException = aex.InnerExceptions.First();
if (firstException.InnerException != null)
firstException = firstException.InnerException;
}
var response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content =
new StringContent(firstException != null
? firstException.ToString()
: "Encountered an AggreggateException without any inner exceptions")
};
return response;
}
}
Not synchronously, second task will be also executed async but chained with first task, therefore only after first task executed.
Seems to be first task - PostAsync was executed with error. Try to catch TPL aggregated exceptions and find more details in inner exceptions collection from AggregateException
For example like here or subscribe to TaskScheduler.UnobservedTaskException and log there all your exceptions