Is it possible to cancel a call to HttpClient.SendAsync()?
I'm sending some data like this:
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "some url");
var multipartFormDataContent = new MultipartFormDataContent();
// ... construction of the MultipartFormDataContent. It contains form data + picture file
requestMessage.Content = multipartFormDataContent;
var response = await client.SendAsync(requestMessage).ConfigureAwait(false);
This code works perfectly, but I need to be able to cancel a request on user demand. Is this possible?
I see that there is an overload of SendAsync that accepts a CancellationToken but I don't know how to use it. I also know about a property called IsCancellationRequested that indicates if a request has been canceled. But how do I go about actually canceling a request?
The SendAsync method supports cancellation. You can use the overload which takes a CancellationToken, which can be canceled any time you like.
You need to use the CancellationTokenSource class for this purpose. The following code shows how to do that.
CancellationTokenSource tokenSource = new CancellationTokenSource();
...
var response = await client.SendAsync(requestMessage, tokenSource.Token)
.ConfigureAwait(false);
When you want to cancel the request, call tokenSource.Cancel(); and you're done.
Important: There is no guarantee that cancelling the CancellationTokenSource will cancel the underlying operation. It depends upon the implementation of the underlying operation (in this case the SendAsync method). The operation could be canceled immediately, after few seconds, or never.
It is worth noting that this is how you'd cancel any method which supports CancellationToken. It will work with any implementation, not just the SendAsync method that is the subject of your question.
For more info, refer to Cancellation in Managed Threads
Related
In an Azure function event hub trigger (v3) it takes in a cancellation token in the Run method. When cancellation is signaled it means the server it shutting down. If I send this token to for example a Get operation using httpClient it will throw a TaskCanceledException and the function will end.
Will the events that this function was processing be sent to another instance of the function on another server or are they lost? Should cancellation be handle in a different way?
[FunctionName(nameof(MyFunction)), FixedDelayRetry(10, "00:00:15")]
public async Task RunAsync(
[EventHubTrigger("%InEventHubName%",
Connection = "InEventHubConnectionString",
ConsumerGroup = "%ConsumerGroup%")]
EventData[] events,
PartitionContext partitionContext,
CancellationToken cancellationToken)
{
foreach (var ev in events)
{
var response = await _httpClient.GetAsync("http://example.com/fetch?key=" + ev.Properties["Key"],
cancellationToken);
await Process(response, cancellationToken);
}
}
Will the events that this function was processing be sent to another instance of the function on another server or are they lost?
They are lost:
Unhandled exceptions may cause you to lose messages. Executions that result in an exception will continue to progress the pointer.
Should cancellation be handle in a different way?
You could choose to ignore cancellation. That may be best for this kind of situation.
I'm trying to set a default timeout for my HttpClient calls to 5 seconds.
I've done this via CancellationTokenSource.
Here's the pertinent bit of code:
var cancellationToken = new CancellationTokenSource();
cancellationToken.CancelAfter(TimeSpan.FromSeconds(5));
var result = _httpClient.SendAsync(request, cancellationToken.Token);
Works as i expected in terms of the calling code getting a "Task was cancelled" error (i tested in a .NET 4.7 console app), but i noticed in Fiddler the request was still running for 1 minute, until it finally gave up:
Can someone explain this behaviour?
I would expect the underlying request to also get cancelled when the cancellation is triggered.
_httpClient is instantiated like: new HttpClient { BaseAddress = baseAddress }
I know there's the the Timeout setting, but not sure if I should be using that or cancellation tokens? My guess is Timeout is for the non-async/await cases?
As Damien said in the comments, HttpClient re-uses connections as much as possible, hence the reason why the connection is not closed on cancel.
When canceling a request like that, the HttpClient will just stop sending/receiving data to/from the other end. It will not send anything to inform the other end that it was cancelled. So the timeout you see of 1 minute depends on the behavior of the other end of your connection.
Also, if you want to cancel each request after 5 seconds, you can as well set the Timeout property of _httpClient to TimeSpan.FromSeconds(5). The behavior will be exactly the same (a TaskCanceledException will be thrown if the other end doesn't respond within 5 seconds).
If anyone is interested, you can try the following approach to applying your own timeout per HttpClient request. It seems to work for me, restricting the SendAsync() to 2 seconds and returning immediately when the timeout occurs:
private async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, TimeSpan? timeout = null)
{
if (timeout is null)
{
return await _httpClient.SendAsync(request);
}
else
{
using (var cts = new CancellationTokenSource(timeout.Value))
{
var sendTask = _httpClient.SendAsync(request);
while (!sendTask.IsCompleted)
{
cts.Token.ThrowIfCancellationRequested();
await Task.Delay(10).ConfigureAwait(false);
}
return await sendTask.ConfigureAwait(false);
}
}
}
Consider the simple MVC5 controller:
public class DocumentsController {
// ctor code is omitted
[HttpPost, Route("api/documents/request/stamp={stamp}")]
public ActionResult RequestDocuments(string stamp) {
var documents = this.DocumentsRequestService.RequestByStamp(stamp);
return new JsonResult(documents);
}
}
The DocumentsRequestService does these things internally:
it sends a request to a dedicated MSMQ-queue (let's call it M) AND synchronously waits for an incoming message at the M's response queue:
using(var requestMessage = new Message()) {
requestMessage.Body = documentStamp;
requestMessage.Recoverable = true;
requestMessage.Label = "request";
requestMessage.ResponseQueue = this.requestedDocumentsResponseQueue;
requestMessage.Formatter = new XmlMessageFormatter(new Type[] { typeof(String) });
// send request
this.requestedDocumentsQueue.Send(requestMessage);
// synchronously wait for response
var responseMessage = this.requestedDocumentsResponseQueue.Receive();
if(responseMessage.Label.EndsWith("success")) {
return new DocumentsRequestResult(
success: true,
matches: parseMatchesList(responseMessage)
);
}
return new DocumentsRequestResult(
success: false,
matches: Enumerable.Empty<DocumentsRequestMatch>()
);
}
the consumer (Windows Service) of that message makes a specific api call. By saying 'specific' I mean that we use a third-party means to do that. This call is synchronous and quite long. When the processing ends the consumer sends a response message to the requesting message's response queue.
when response arrives at the M's response queue it's a time to parse and return the results to the controller.
From the end user's perspective this task should be blocking, or at least it should look like blocking.
As far as I understand running a Task makes use of parallelization. Whereas using the async-await pair makes the running task asynchronous. It could be helpful if several tasks would run in parallel.
Is it reasonable/possible to incorporate with Tasking/Asynchrony in my case? If yes, then where do I start?
The "asynchrony" of a network call is transparent to the caller. It doesn't matter to the caller whether the implementation is synchronous or asynchronous. Put another way, from a client's perspective, it's always asynchronous.
For example, the HTTP client couldn't care less if RequestDocuments is synchronous or asynchronous; either way, the HTTP client will send a request and receive a response some time later (i.e., asynchronously).
Similarly, the HTTP web server doesn't care whether the Win32 service is implemented synchronously or asynchronously. It just knows that it puts a message on a queue and some time later (i.e., asynchronously) it gets a response message from the queue.
As far as I understand running a Task makes use of parallelization. Whereas using the async-await pair makes the running task asynchronous.
Sort of. Task can be used for either asynchronous or parallel code, a fact that has caused much confusion. However, Task Parallel Library constructs such as Parallel and PLINQ are firmly in the parallel (non-asynchronous) world.
It could be helpful if several tasks would run in parallel.
I believe "concurrently" is the appropriate term here.
First, note that ASP.NET gives you a considerable amount of concurrency for free. If you want to make each request internally concurrent, then you can do so fairly easily via Task.WhenAll. For example, you can change your DocumentsRequestService call to be asynchronous (assuming your message queue API supports async calls):
using(var requestMessage = new Message()) {
...
// send request
await this.requestedDocumentsQueue.SendAsync(requestMessage);
// asynchronously wait for response
var responseMessage = await this.requestedDocumentsResponseQueue.ReceiveAsync();
...
}
Then you can call it multiple times simultaneously from a single controller action as such:
public async Task<ActionResult> RequestDocuments(string stamp1, string stamp2) {
var task1 = this.DocumentsRequestService.RequestByStampAsync(stamp1);
var task2 = this.DocumentsRequestService.RequestByStampAsync(stamp2);
var documents = await Task.WhenAll(task1, task2);
return new JsonResult(documents);
}
I need to change a HttpClient.Timeout property after it made a request(s). When I try, I get an exception:
This instance has already started one or more requests. Properties can only be modified before sending the first request.
Is there any way to avoid this?
There isn't much you can do to change this. This is just default behavior in the HttpClient implementation.
The Timeout property must be set before the GetRequestStream or GetResponse method is called.
From HttpClient.Timeout Remark Section
In order to change the timeout, it would be best to create a new instance of an HttpClient.
client = new HttpClient();
client.Timeout = 20; //set new timeout
Internally the Timeout property is used to set up a CancellationTokenSource which will abort the async operation when that timeout is reached. Since some overloads of the HttpClient methods accept CancellationTokens, we can create helper methods to have a custom timeouts for specific operations:
public async Task<string> GetStringAsync(string requestUri, TimeSpan timeout)
{
using (var cts = new CancellationTokenSource(timeout))
{
HttpResponseMessage response = await _httpClient.GetAsync(requestUri, cts.Token)
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
Lack of support for custom request-level timeouts has always been a shortcoming of HttpClient in my mind. If you don't mind a small library dependency, Flurl.Http [disclaimer: I'm the author] supports this directly:
"http://api.com/endpoint".WithTimeout(30).GetJsonAsync<T>();
This is a true request-level setting; all calls to the same host use a shared HttpClient instance under the hood, and concurrent calls with different timeouts will not conflict. There's a configurable global default (100 seconds initially, same as HttpClient).
We have a .NET application that uses an HTTP based API where we POST a request to a third party HTTP endpoint (that is not under our control) and it calls us back at a later time on an HTTP endpoint that we give it; something like:
WebRequest request = WebRequest.Create(urlToMethod);
request.Method = #"POST";
request.Headers.Add(#"Callback", "http://ourserver?id="+id );
We make thousands upon thousands of these calls and so we'd like to be as effecient as possible (in terms of speed/memory/threads etc.)
As far as the callback code is concerned, we have a type that acts as a listener; this is how we start it up:
_httpListener = new HttpListener();
_httpListener.Prefixes.Add(ourServer);
_httpListener.Start();
_httpListener.BeginGetContext(callback, null);
When the server calls us back, it hits our callback method which looks something like this:
HttpListenerContext context = _httpListener.EndGetContext(result);
HttpListenerResponse httpListenerResponse = context.Response;
httpListenerResponse.StatusCode = 200;
httpListenerResponse.ContentLength64 = _acknowledgementBytes.Length;
var output = httpListenerResponse.OutputStream;
output.Write(_acknowledgementBytes, 0, _acknowledgementBytes.Length);
context.Response.Close();
var handler = ResponseReceived;
if (handler != null)
{
handler(this, someData);
}
So we have a single instance of this listener (_which internally uses HttpListener) and for every response it gets, it informs all of the subscribers on the ResponseReceived event.
The subscribers (possibly hundreds of them) only care about data associated with their particular id. The subscribers look something like:
_matchingResponseReceived = new ManualResetEventSlim(false);
_listener.WhenResponseReceived += checkTheIdOfWhatWeGetAndSetTheEventIfItMatches;
postTheMessage();
_matchingResponseReceived.Wait(someTimeout);
It's that last line that's bugging me. We post the message but then block the whole thread waiting for the Listener to get a response and call our event handler. We'd like to use Tasks but doesn't seem like it'll give us much if we're blocking a whole thread waiting for the callback.
Is there a better (more TPL friendly) way of achieving this so that no threads are blocked and we get fire off more requests simultaneously?
async-await together with TaskCompletionSource pretty much were made for this.
The sender side creates a TaskCompletionSource, adds it to a dictionary (with key being the id of the request), makes the request and returns the TaskCompletionSource's Task.
The receiver then looks into the dictionary to find the right TaskCompletionSource, removes it from there and sets its result.
The caller of the sender method will await the returned Task, which will asynchronously wait for the receiver to process the callback call.
In code, it could look something like this:
// TODO: this probably needs to be thread-safe
// you can use ConcurrentDictionary for that
Dictionary<int, TaskCompletionSource<Result>> requestTcses;
public async Task<Result> MakeApiRequestAsync()
{
int id = …;
var tcs = new TaskCompletionSource<Result>();
requestTcses.Add(id, tcs);
await SendRequestAsync(id);
return await tcs.Task;
}
…
var result = await MakeApiRequest();
var context = await _httpListener.GetContext();
// parse the response into id and result
var tcs = requestTcses[id];
requestTcses.Remove(id);
tcs.SetResult(result);
This whole architecture seems to be more complicated than it should be (I might have not understood your program right).
Why not post your request to the second server (BTW, you don't need string literal for "POST") and end the routine, then get the request from that server in a regular Web API method, parse the data to find the IDs, and execute thread for each ID?