I have to send an async POST to a web service and I want to send/retrieve the data asynchronously.
I also need to send the request but only wait a maximum of 1500 ms waiting for a response. If I don't get a response, the program should continue on (this is a service making an external web service call). I want to offload these service calls to IOCP's instead of blocking for a long time and waiting for them to return. I only want to block for 1500 ms total.
Here's what I have so far:
var httpRequest = (HttpWebRequest)WebRequest.Create(#"urltoPostTo");
httpRequest.Method = "POST";
byte[] data = Encoding.ASCII.GetBytes("test-post");
httpRequest.ContentLength = data.Length;
var asyncTask = Task.Factory.FromAsync<Stream>(httpRequest.BeginGetRequestStream, httpRequest.EndGetRequestStream, httpRequest)
.ContinueWith(response =>
{
var localStream = response.Result;
if (localStream != null)
{
localStream.Write(data, 0, data.Length);
localStream.Close();
}
}); //how do I do the continuation for BeginGetResponse and EndGetResponse from here?
I have a couple of requirements that unfortunately I can't change.
I am using Visual Studio 2010 targeting 4.0
I cannot use the Async BCL
I would like to try and use Task.FromAsync
This has already been answered in Implementing extension method WebRequest.GetResponseAsync with support for CancellationToken with a GetResponseAsync extension method that properly handles timeouts and CancellationTokens. When Request.Timeout expires, the method calls Request.Abort before returning the Task in its cancelled state.
The reason for such involved coding is that it's the client's (yours) responsibility to properly handle timeouts, so you can't depend on FromAsync to handle the timeout expiration. Perhaps this is the reason why FromAsync doesn't accept a cancellation token.
Another option is to avoid cancelling the request itself and cancel the continuation. You can use the ContinueWith overload that accepts a CancellationToken and call CancellationTokenSource.CancelAfter to set the cancellation timeout.
This would allow your code to ignore the results and keep running but it wouldn't break the connection to the server and wouldn't stop the background thread from processing any potentially expensive results.
You could write something like this:
var tcs=new CancellationTokenSource();
var asyncTask = Task.Factory.FromAsync<Stream>(httpRequest.BeginGetRequestStream, httpRequest.EndGetRequestStream, httpRequest)
.ContinueWith(response =>
{...},
cts.Token);
cts.CancelAfter(1500);
Note that the call to CancelAfter is done after starting the asynchronous task.
I would prefer Reed Copsey's extension method in a busy site because a high number of cancelled but outstanding requests can easily exhaust the thread pool, consume a lot of memory without a reason and consume potentially expensive connections to external systems.
why not to work with HttpClient?
webApiHttpClient.PostAsJsonAsync(GetFullAPI("api/Subscribe"), obj)
.ContinueWith(res => _logger.InfoFormat("Subscribe result: {0}", res.Result.StatusCode));
Try this method helper.
public static Task<string> Post(string url, Encoding encoding, string content)
{
var httpRequest = (HttpWebRequest)WebRequest.Create(url);
httpRequest.Method = "POST";
byte[] data = encoding.GetBytes(content);
httpRequest.ContentLength = data.Length;
TaskCompletionSource<string> result = new TaskCompletionSource<string>();
Task.Factory.FromAsync<Stream>(httpRequest.BeginGetRequestStream, httpRequest.EndGetRequestStream, httpRequest)
.ContinueWith(requestStreamTask =>
{
try
{
using (var localStream = requestStreamTask.Result)
{
localStream.Write(data, 0, data.Length);
localStream.Flush();
}
Task.Factory.FromAsync<WebResponse>(httpRequest.BeginGetResponse, httpRequest.EndGetResponse, httpRequest)
.ContinueWith(responseTask =>
{
try
{
using (var webResponse = responseTask.Result)
using (var responseStream = webResponse.GetResponseStream())
using (var sr = new StreamReader(responseStream, encoding))
{
result.SetResult(sr.ReadToEnd());
}
}
catch (Exception e)
{
result.SetException(e);
}
}, TaskContinuationOptions.AttachedToParent);
}
catch (Exception e)
{
result.SetException(e);
}
}, TaskContinuationOptions.AttachedToParent);
return result.Task;
}
Related
I initialise HttpClient like so:
public static CookieContainer cookieContainer = new CookieContainer();
public static HttpClient httpClient = new HttpClient(new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, CookieContainer = cookieContainer }) { Timeout = TimeSpan.FromSeconds(120) };
so all queries should throw TaskCanceledException if no response is received within 120 seconds.
But some queries (like 1 of 100 000-1 000 000) hang infinitely.
I wrote following code:
public static async Task<HttpResponse> DownloadAsync2(HttpRequestMessage httpRequestMessage)
{
HttpResponse response = new HttpResponse { Success = false, StatusCode = (int)HttpStatusCode.RequestTimeout, Response = "Timeout????????" };
Task task;
if (await Task.WhenAny(
task = Task.Run(async () =>
{
try
{
HttpResponseMessage r = await Global.httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false);
response = new HttpResponse { Success = true, StatusCode = (int)r.StatusCode, Response = await r.Content.ReadAsStringAsync().ConfigureAwait(false) };
}
catch (TaskCanceledException)
{
response = new HttpResponse { Success = false, StatusCode = (int)HttpStatusCode.RequestTimeout, Response = "Timeout" };
}
catch (Exception ex)
{
response = new HttpResponse { Success = false, StatusCode = -1, Response = ex.Message + ": " + ex.InnerException };
}
}),
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(150)).ConfigureAwait(false);
})
).ConfigureAwait(false) != task)
{
Log("150 seconds passed");
}
return response;
}
which actually occasionally executes Log("150 seconds passed");.
I call it like so:
HttpResponse r = await DownloadAsync2(new HttpRequestMessage
{
RequestUri = new Uri("https://address.com"),
Method = HttpMethod.Get
}).ConfigureAwait(false);
Why TaskCanceledException sometimes isn't thrown after 120 seconds?
I don't know with how frequency you call DownloadAsync2, but your code smells a lot for bursting and starving ThreadPool.
Initial number of threads in ThreadPool by default is limited to number of CPU logical cores (usually 12 for today normal systems) and in case of unavailability of threads in ThreadPool, 500ms takes for each new thread to be generated.
So for example:
for (int i = 0; i < 1000; i++)
{
HttpResponse r = await DownloadAsync2(new HttpRequestMessage
{
RequestUri = new Uri("https://address.com"),
Method = HttpMethod.Get
}).ConfigureAwait(false);
}
This code with a high chance will be freezed, specially if you have some lock or any cpu intensive tasks somewhere in your code. Because you invoke new thread per calling DownloadAsync2 so all threads of ThreadPool consumed and many more of them still needed.
I know maybe you say "all of my tasks have been awaited and they release for other works". but they also consumed for starting new DownloadAsync2 threads and you will reach the point that after finishing await Global.httpClient.SendAsync no thread remains for re-assigning and completing the task.
So method have to wait until one thread being available or generated to complete (even after timeout). Rare but feasible.
On Windows & .NET, the number of concurrent outgoing HTTP request to the same endpoint is limited to 2 (as per HTTP 1.1 specification). If you create a ton of concurrent requests to the same endpoint they will queue up. That is one possible explanation to what you experience.
Another possible explanation is this: you don't set the Timeout property of HttpClient explicitly, so it defaults to 100 seconds. If you keep making new requests, while the previous ones didn't finish, system resources will become used up.
I suggest setting the Timeout property to a low value - something proportional to the frequency of the calls you make (1 sec?) and optionally increasing the number of conncurrent outgoing connections with ServicePointManager.DefaultConnectionLimit
I discovered it was httpClient.SendAsync method which occasionally hangs. Therefore I added a cancellation token set to X seconds. But even with a cancelletion token it may sometimes remain stuck and never throw TaskCanceledException.
Therefore I proceeded to workaround that keeps the SendAsync task forever stuck on background and continue with other work.
Here is my workaround:
public static async Task<Response> DownloadAsync3(HttpRequestMessage httpRequestMessage, string caller)
{
Response response;
try
{
using CancellationTokenSource timeoutCTS = new CancellationTokenSource(httpTimeoutSec * 1000);
using HttpResponseMessage r = await Global.httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead, timeoutCTS.Token).WithCancellation(timeoutCTS.Token).ConfigureAwait(false);
response = new Response { Success = true, StatusCode = (int)r.StatusCode, Message = await r.Content.ReadAsStringAsync().ConfigureAwait(false) };
}
catch (TaskCanceledException)
{
response = new Response { Success = false, StatusCode = (int)HttpStatusCode.RequestTimeout, Message = "Timeout" };
}
catch (Exception ex)
{
response = new Response { Success = false, StatusCode = -1, Message = ex.Message + ": " + ex.InnerException };
}
httpRequestMessage.Dispose();
return response;
}
public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
return task.IsCompleted
? task
: task.ContinueWith(
completedTask => completedTask.GetAwaiter().GetResult(),
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
With Flurl, you can configure Timeout per client, per request or globally.
// call once at application startup
FlurlHttp.Configure(settings => settings.Timeout = TimeSpan.FromSeconds(120));
string url = "https://address.com";
// high level scenario
var response = await url.GetAsync();
// low level scenario
await url.SendAsync(
HttpMethod.Get, // Example
httpContent, // optional
cancellationToken, // optional
HttpCompletionOption.ResponseHeaderRead); // optional
// Timeout at request level
await url
.WithTimeout(TimeSpan.FromSeconds(120))
.GetAsync();
Fluent HTTP documentation
Flurl configuration documentation
And the answer is:
ThreadPool.SetMinThreads(MAX_THREAD_COUNT, MAX_THREAD_COUNT);
where MAX_THREAD_COUNT is some number (I use 200). You MUST set at least the second parameter (completionPortThreads), and most probably the first (workerThreads). I had already set the first, but not the second, and now that it is working I am keeping both set.
Alas, this isn't the answer. See comments below
OK, I officially give up! I'm replacing the code with:
try
{
return Task.Run(() => httpClient.SendAsync(requestMessage)).Result;
}
catch (AggregateException e)
{
if (e.InnerException != null)
throw e.InnerException;
throw;
}
We are using an HttpClient to post json to a restful web service. In one instance, we are running into something that has us baffled. Using tools like postman, fiddler etc, we can post to an endpoint and see that it is working. When we do the same with HttpClient.PostAsJsonAsync, we can verify in the software we are posting to that it received the data just fine. However, our PostAsJsonAsync will always eventually time out rather than give us a response.
We have worked with the team that created the service we are consuming, plus our additional testing on our side, and we have not yet been able to truly time out that service.
Every time we do a post with HttpClient, we then can verify that the target software we post to does indeed get the data. Any time we do a post to that target software from any other tool, we always very quickly see a response with status code of 200. Something about HttpClient is failing to accept the response from this particular service. Does anyone have an idea what we can look at from here?
Here's the code (though it is so cookie cutter that I hardly feel it is needed)
public string PostData(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
using (var client = new HttpClient())
{
if (timeoutMinutes > 0)
{
client.Timeout = new TimeSpan(0,timeoutMinutes,0);
}
var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
var response = client.PostAsJsonAsync(useUrl, o).Result;
if(response.StatusCode == System.Net.HttpStatusCode.OK)
{
return response.Content.ReadAsStringAsync().Result;
}
return "";
}
}
This:
var response = client.PostAsJsonAsync(useUrl, o).Result;
Is causing you code to deadlock. This is often the case when blocking on async API's, and that's why you're experiencing the "I don't see any response coming back" effect.
How is this causing a deadlock? The fact that you are executing this request in an environment that contains a synchronization context, perhaps one which belongs to the UI. It's executing the async request, and when the response arrives, it continues via an IO completion thread which attempts to post the continuation onto that same UI context, which is currently blocked by your .Result call.
If you want to make an HTTP request synchronously, use WebClient instead. If you want to take advantage of the async API properly, then await instead of block with .Result.
I had the same issue and this SO answer fixed it for me.
In a nutshell, you have to use the ConfigureAwait(false) extension to avoid the deadlock:
var response = await client.PostAsJsonAsync(useUrl, o).ConfigureAwait(false);
Is there a reason why you're not following the async await pattern? You're calling an async method, but not awaiting it. You didn't say if the code calling your REST service was a Windows Forms or ASP.NET application, but that .Result is probably causing you issues.
Can you restructure your method like this:
public async Task<string> PostData(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
using (var client = new HttpClient())
{
if (timeoutMinutes > 0)
{
client.Timeout = new TimeSpan(0,timeoutMinutes,0);
}
var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
var response = await client.PostAsJsonAsync(useUrl, o);
if(response.StatusCode == System.Net.HttpStatusCode.OK)
{
return await response.Content.ReadAsStringAsync();
}
return "";
}
}
This is a slight modification to #Justin Helgerson's solution. There are 2 blocking .Result calls in your method; once you go async you should fix them both.
public async Task<string> PostDataAsync(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
using (var client = new HttpClient())
{
if (timeoutMinutes > 0)
{
client.Timeout = new TimeSpan(0,timeoutMinutes,0);
}
var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
var response = await client.PostAsJsonAsync(useUrl, o);
if(response.StatusCode == System.Net.HttpStatusCode.OK)
{
return await response.Content.ReadAsStringAsync();
}
return "";
}
}
Note I've also renamed the method to PostDataAsync in accordance with the TAP pattern.
System.Net.ServicePointManager.Expect100Continue = false;
That one line of code in our case fixed the problem. A developer from a different team offered that suggestion, and it works. I have yet to google it and read up on it enough to offer an explanation of what that is addressing.
I want to iterate a batch of requests, sending each one of them to an external API using HttpClient class.
foreach (var MyRequest in RequestsBatch)
{
try
{
HttpClient httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromMilliseconds(5);
HttpResponseMessage response = await httpClient.PostAsJsonAsync<string>(string.Format("{0}api/GetResponse", endpoint), myRequest);
JObject resultResponse = await response.Content.ReadAsAsync<JObject>();
}
catch (Exception ex)
{
continue;
}
}
The context here is I need to set a very small timeout value, so in case the response takes more than that time, we simply get the "Task was cancelled" exception and continue iterating.
Now, in the code above, comment these two lines:
HttpResponseMessage response = await httpClient.PostAsJsonAsync<string>(string.Format("{0}api/GetResponse", endpoint), myRequest);
resultResponse = await response.Content.ReadAsAsync<JObject>();
The iteration ends very fast. Uncomment them and try again. It takes a lot of time.
I wonder if calling PostAsJsonAsync/ReadAsAsync methods with await takes more time than the timeout value?
Based on the answer below, supposing it will create different threads, we have this method:
public Task<JObject> GetResponse(string endPoint, JObject request, TimeSpan timeout)
{
return Task.Run(async () =>
{
try
{
HttpClient httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromMilliseconds(5);
HttpResponseMessage response = await httpClient.PostAsJsonAsync<string>(string.Format("{0}api/GetResponse", endPoint), request).WithTimeout<HttpResponseMessage>(timeout);
JObject resultResponse = await response.Content.ReadAsAsync<JObject>().WithTimeout<JObject>(timeout);
return resultResponse;
}
catch (Exception ex)
{
return new JObject() { new JProperty("ControlledException", "Invalid response. ")};
}
});
}
An exception is raised there and the JObject exception should be returned, very fast, however, if using httpClient methods, even if it raises the exception it takes a lot of time. Is there a behind the scenes processing affecting the Task even if the return value was a simple exception JObject?
If yes, which another approach could be used to send a batch of requests to an API in a very fast way?
I agree with the accepted answer in that the key to speeding things up is to run the requests in parallel. But any solution that forces additional threads into the mix by use of Task.Run or Parallel.ForEach is not gaining you any efficiency with I/O bound asynchronous operations. If anything it's hurting.
You can easily get all calls running concurrently while letting the underlying async subsystems decide how many threads are required to complete the tasks as efficiently as possible. Chances are that number is much smaller than the number of concurrent calls, because they don't require any thread at all while they're awaiting a response.
Further, the accepted answer creates a new instance of HttpClient for each call. Don't do that either - bad things can happen.
Here's a modified version of the accepted answer:
var httpClient = new HttpClient {
Timeout = TimeSpan.FromMilliseconds(5)
};
var taskList = new List<Task<JObject>>();
foreach (var myRequest in RequestsBatch)
{
// by virtue of not awaiting each call, you've already acheived parallelism
taskList.Add(GetResponseAsync(endPoint, myRequest));
}
try
{
// asynchronously wait until all tasks are complete
await Task.WhenAll(taskList.ToArray());
}
catch (Exception ex)
{
}
async Task<JObject> GetResponseAsync(string endPoint, string myRequest)
{
// no Task.Run here!
var response = await httpClient.PostAsJsonAsync<string>(
string.Format("{0}api/GetResponse", endpoint),
myRequest);
return await response.Content.ReadAsAsync<JObject>();
}
It doesn't look like you're actually running a seperate thread for each request. Try something like this:
var taskList = new List<Task<JObject>>();
foreach (var myRequest in RequestsBatch)
{
taskList.Add(GetResponse(endPoint, myRequest));
}
try
{
Task.WaitAll(taskList.ToArray());
}
catch (Exception ex)
{
}
public Task<JObject> GetResponse(string endPoint, string myRequest)
{
return Task.Run(() =>
{
HttpClient httpClient = new HttpClient();
HttpResponseMessage response = httpClient.PostAsJsonAsync<string>(
string.Format("{0}api/GetResponse", endpoint),
myRequest,
new CancellationTokenSource(TimeSpan.FromMilliseconds(5)).Token);
JObject resultResponse = response.Content.ReadAsAsync<JObject>();
});
}
In my app I need to do lot of parallel http requests and I have read that it is proper to do it using async/await. In each request I need to get string content from it (often it is html of some site) and my question is: how can I do it in best way?
My current implementation:
public static async Task<string> GetStringContentAsync(HttpWebRequest webRequest)
{
try
{
using (var response = (HttpWebResponse) await webRequest.GetResponseAsync()
.ConfigureAwait(false))
{
var content = await GetStringContentFromResponseAsync(response)
.ConfigureAwait(false);
return content;
}
}
catch (Exception exception)
{
return null;
}
}
private static async Task<string> GetStringContentFromResponseAsync(HttpWebResponse response)
{
using (var responseStream = GetResponseStream(response))
{
if (responseStream == null)
return null;
using (var streamReader = new StreamReader(responseStream))
{
var content = await streamReader.ReadToEndAsync()
.ConfigureAwait(false);
return content;
}
}
}
private static Stream GetResponseStream(HttpWebResponse webResponse)
{
var responseStream = webResponse.GetResponseStream();
if (responseStream == null)
return null;
Stream stream;
switch (webResponse.ContentEncoding.ToUpperInvariant())
{
case "GZIP":
stream = new GZipStream(responseStream, CompressionMode.Decompress);
break;
case "DEFLATE":
stream = new DeflateStream(responseStream, CompressionMode.Decompress);
break;
default:
stream = responseStream;
break;
}
return stream;
}
And example of using:
var httpWebRequest = (HttpWebRequest) WebRequest.Create("http://stackoverflow.com/");
var content = await HttpHelper.GetStringContentAsync(httpWebRequest)
.ConfigureAwait(false);
Is this correct implementation, or we can improve something here? Maybe I'm doing some overhead when using async/await when reading stream?
Reason of my question is that when I'm using my code like this:
for(var i=0;i<1000;i++)
{
Task.Run(()=>{
var httpWebRequest = (HttpWebRequest) WebRequest.Create("http://google.com/");
var content = await HttpHelper.GetStringContentAsync(httpWebRequest)
.ConfigureAwait(false);
});
}
this tasks take to long to execute, but one request to google is very fast. I thought that async requests in this example must be ready almost in same time and this time must be pretty close to "one google request" time.
EDIT:
I forgot to say that I know about ServicePointManager.DefaultConnectionLimit and set it 5000 in my app. So it is not a problem.
I can't use HttpClient because my final goal is to do 100-300 requests at one time from different proxies. And if I understand right, HttpClient can work with only one proxy at one time and can't setup each request separately.
That's a tricky one. Since you know about DefaultConnectionLimit, it's already something good, but there is one more interesting and rather surprising thing:
httpRequest.ServicePoint.ConnectionLeaseTimeout
httpRequest.ServicePoint.MaxIdleTime
Information is here, your latencies might be caused by its default behavior and connections being held to ServicePoint while trying to make next request
Here's the answer answer to your issue: https://msdn.microsoft.com/en-us/library/86wf6409(v=vs.90).aspx
Using synchronous calls in asynchronous callback methods can result in severe performance penalties. Internet requests made with WebRequest and its descendants must use Stream.BeginRead to read the stream returned by the WebResponse.GetResponseStream method.
That means absolutely no synchronous code (including awaits) when reading the response stream. But even that isn't enough, as DNS lookups and TCP connection are still blocking. If you can use .NET 4.0, there's a much more easy to use System.Net.Http.HttpClient class. Otherwise, you can use System.Threading.ThreadPool, which is the workaround I ended up using on 3.5:
ThreadPool.QueueUserWorkItem((o) => {
// make a synchronous request via HttpWebRequest
});
Is there a way, using HttpClient, to differentiate a time-out "when we get no response from the server" from a "time exceeded" operation?
Let me explain our issue:
Case1: If we don't get any response from the server in 10 seconds then this is an issue.
Case2: If we get a response from the server, but the server continues to transfer data and it takes a while, maybe 30 seconds or more. Then this is not an issue.
Is there a way using .NET HttpClient class to handle this scenario? From what I tested specifying a TimeOut on HttpClient will put the same time-out for case1 and case2.
Here is the solution I managed to do:
// Create the cancelation token, when we don't get any feedback from server within 20 seconds
var cancelHeadersToken = new CancellationTokenSource();
cancelHeadersToken.CancelAfter(TimeSpan.FromSeconds(20)); // if we don't receive server headers after 20 seconds then something went wrong
// We have another cancelation token, that allows the user to cancel the request, so here we create a linked token source which uses both tokens
var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(userCancelToken, cancelHeadersToken.Token);
// The linked token is then used in GetAsync and we use the overload which allows to specify the HttpCompletionOption
// We only want to receive headers and not all content
var httpMessage = await customClient.CustomizedHttpClient.GetAsync(address, HttpCompletionOption.ResponseHeadersRead, linkedToken.Token).ConfigureAwait(false);
// We can then download the content, and we still allow to cancel anything by the user
using (var memoryStream = new MemoryStream(100000)) { // 100ko by default
using (var stream = await httpMessage.Content.ReadAsStreamAsync().ConfigureAwait(false)) {
await stream.CopyToAsync(memoryStream, 10000, userCancelToken).ConfigureAwait(false); // copy to memory stream 10ko per 10ko
}
string data = "";
if (memoryStream.Length > 0) {
var headers = httpMessage.Content.Headers;
Encoding encoding;
if (headers != null && headers.ContentType != null && headers.ContentType.CharSet != null) {
encoding = Encoding.GetEncoding(headers.ContentType.CharSet);
} else {
encoding = Encoding.UTF8;
}
data = encoding.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
}
// Then you do whatever you want with data
}
You can use one of the variants of the various methods that accepts a CancellationToken.
If the cancel happens after the method's Task has completed, the cancellation is ignored and you can continue with e.g. processing the content from the result.
var client = new HttpClient();
var cancel = new CancellationTokenSource();
cancel.CancelAfter(TimeSpan.FromSeconds(10));
var resp = client.GetAsync("http://www.google.co.uk", cancel.Token).Result;
So, in the above, provided we get enough back from the server for the GetAsync to complete, the cancellation has no effect.