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.
Related
(This discussion might not be specific to C#...)
I have a C# method SendMultipleRequests that sends HTTP POST request 10 times sequentially.
Is it possible for the server to receive requests out of order?
If my understanding is correct if the requests are sent concurrently (without await), the server could receive requests out of order, but in the example below it needs to wait for the response to be received at the client before sending next request, so the server will receive requests in order.
public async Task SendRequest(int i)
{
// definition of endpoint is omitted in this example
var content = new StringContent($"I am {i}-th request");
await HttpClient.PostAsync(endpoint, content);
}
public async Task SendMultipleRequests()
{
for (int i = 0; i < 10; i++)
{
await SendRequest(i);
}
}
with await your app will wait for the task returned by PostAsync to finish before it issues the next request - see the docs for postasync where it says “This operation will not block. The returned Task<TResult> object will complete after the whole response (including content) is read.” - using await will mean that you will only issue the next request after you I’ve read the content of the previous response
If you remove the await then your code will queue up ten tasks and start working on them all in some undefined order. The server will see requests in an unspecified order. This may be further exacerbated by the fact that the requests may take different routes through the internet, some slower. If you remove the await then you should capture the returned task into a list, then you can use something like await Task.WhenAll(list) to wait for them all to complete (unless you really want to fire and forget in which case you can assign them to the discard _ = client.PostAsync... but keeping the task allows you to discover and deal with exceptions that arose)
var response = SaveOrderInDB();
OrderCreatedEvent orderCreatedEvent = new OrderCreatedEvent(x, y, z);
_requestRouter.Publish(orderCreatedEvent);
return response;
By MediatR docs the notifications is "Fire and forget" feature. I do not use await since I want to return immediately "response" object to client Angular app after notification was published.
However when I put breakpoint in notification handler I see in Chrom dev tools that request still in pending status, waits for notification to finish.
public Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
{
return Task.CompletedTask; // Breakpoint is here
}
How can I not wait for notifications to finish?
As mentioned in the comments, the breakpoint is preventing them from completing.
If you don't believe this, change your NotificationHandler to something like:
public async Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
{
await Task.Delay(5000);
Console.Write("Done."); //put a breakpoint here
}
Put a breakpoint on the Console.Write method, then run your application, and call your endpoint.
You'll see the response isn't pending, and after 5 seconds, your breakpoint is hit.
(or "Done." is written to the console if you didn't set a breakpoint)
MediatR's Notifications aren't "Fire and forget" by default. The docs state under the heading "Publish strategies".
The default implementation of Publish loops through the notification handlers and awaits each one. This ensures each handler is run after one another.
If you want to have notifications not be awaited on you will have to override the behaviour of the PublishCore method in the mediator.cs class.
The documentation mentions this can be done under the same header above and points to some sample code under MediatR.Examples.PublishStrategies. Have a look at the method ParallelNoWait in Publisher.cs in that sample to see how it works.
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);
}
}
}
I have a currently working controller method like the following in one project:
[HttpGet]
public async Task<IHttpActionResult> GetReport([FromUri] ReportParamiters ReportRequest, CancellationToken cancellationToken)
{…}
But this may be called from another project using the following to make a pass-through call, dependant on who/where the caller is:
[HttpGet]
public async Task<HttpResponseMessage> GetReport([FromUri] ReportParameters ReportParameters, CancellationToken cancellationToken)
{
using(HttpClient client = this.MessageTransferHelper.BuildJsonHttpClient(Helper.BearerToken(this.Url.Request.Headers.Authorization.ToString())))
{
HttpResponseMessage response =
await
client.GetAsync(this.ConfigurationService.ReportsUrl() + "report1/?DateFrom=" +
ReportParameters.DateFrom.ToString("MM-dd-yyyy") + "&DateTo=" + ReportParameters.DateTo.ToString("MM-dd-yyyy") +
"&valueList=" + String.Join("&valueList=", ReportParameters.Stores.ToArray()));
return response;
The second example simply creates the url and passes it to the actual controller held in the first example, and returns the results. I've searched Google for an answer to this, but can't find anything that matches.
Can I create a cancellation token passed into the GET of the second example, as shown, and then pass it on (along with the relevant search information) into the other GET ? (which is running on another, remote, server)
You can certainly pass a CancellationToken to GetAsync. It won't be the same cancellation token as the remote server will get, but they will be logically connected.
The CancellationToken in the remote GetReport may(1) be triggered if the calling process closes the HTTP socket. The local GetReport can pass a CancellationToken to HttpClient, which would cause it to cancel the request by closing its socket. If the local GetReport passes on the CancellationToken that it gets, then the following should(1) happen:
If the caller of the local GetReport (i.e., an end-user browser) cancels its request (i.e., user closes the browser tab), then the local GetReport's CancellationToken is(1) canceled.
This causes the HttpClient to cancel its request.
This causes(1) the remote GetReport's CancellationToken to cancel.
(1) It should be cancelled in theory. In reality, there's a rat's nest of bugs in ASP.NET cancellation, with the end result that the cancellation is not really guaranteed to happen. I expect this will be fixed in ASP.NET Core.
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