Time out reversal - c#

I need to integrate an API into my development with a specific scenario called "Time Out Reversal" (TOR)
This means, succintly, to comply with the following requirements:
Initiate a request by invoking an endpoint
If a response is not received within a defined time out
start a reversal request by invoking another endpoint
While the requirements seems very clear to me, I really haven't found a way to implement it by using tasks.
For example, I know how to delay a task but I dont really know how to set a timeout in seconds for a started task
Any suggestion?

You can easily implement a timeout like so:
public async Task TimoutReversal() {
var timeout = TimeSpan.FromSeconds(10);
try {
//assumes HttpClient
Client.Timeout = timeout;
await Client.GetAsync(firstEndpoint);
} catch (OperationCanceledException ex) {
await Client.DeleteAsync(secondEndpoint);
}
}
//or
public async Task TimoutReversal() {
var timeout = TimeSpan.FromSeconds(10);
var firstTask = Client.GetAsync(firstEndpoint);
if (await Task.WhenAny(firstTask, Task.Delay(timeout)) == firstTask) {
//success
}else {
//failure
await Client.DeleteAsync(secondEndpoint);
}
}
Also see protected answer here on SO: Task with timeout
Another option is to pass the Token from a CancellationTokenSource created from a TimeSpan to the HttpClient calls.

Related

How to cancel an async connect method after X seconds in C#

I am creating a mqtt client in C# with the libray MQTTNet.
I wan't my client to connect to a broker and stop after 1 second if doesn't succeed.
Here is the function I made below.
private async Task TryConnect(MqttClientOptions options)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
mqttClient!.ConnectAsync(options, tokenSource.Token);
await Task.Delay(1000);
tokenSource.Cancel();
}
The method is working but it gives me a warning when I call the method ConnectAsync because I am not using an await operator before the call. And if I use the await operator the method will continue until it will raise an error.
Is there a way to do this without warnings ? Because even if it is working I have the feeling that this is not the better way to do it and that there is a cleaner way.
Thank you for your help,
Emmanuel
You should probably use CancelAfter:
CancellationTokenSource tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(1));
await mqttClient!.ConnectAsync(options, tokenSource.Token);
An alternative would be to store the task from connectAsync and await if after the cancel call.
Note that in either case you are not guaranteed that the connection will actually cancel, it entirely depends on the ConnectAsync-implementation. In some cases it might be approrpiate to use Task.WhenAny to await either the connection, or a Task.Delay, i.e. you do no longer care about the connection after the timeout. You should probably also catch OperationCancelledException, since that is the standard method to communicate that the operation actually was cancelled.
You can specify a timeout for a cancellation token:
var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1));
If you're cancelling it like this you will need to catch a TaskCanceledException, but you can ignore the cancellation if you want.
private async Task TryConnect(MqttClientOptions options)
{
var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1));
try
{
mqttClient!.ConnectAsync(options, tokenSource.Token);
}
catch (TaskCanceledException)
{
// Do nothing.
}
}
Alternatively you could return a bool to indicate whether the connection was successful:
private async Task<bool> TryConnect(MqttClientOptions options)
{
var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1));
try
{
mqttClient!.ConnectAsync(options, tokenSource.Token);
}
catch (TaskCanceledException)
{
return false;
}
return true;
}

hangfire job cancellation doesn't happen

I have .Net core 3.1 Web api project. There is a method to call separate API for getting some values.
This is my code
Define instance variable
CancellationTokenSource tokenSource = new CancellationTokenSource();
Looping through my values. There is separate method and user call the method when he wants.
foreach (var no in nos)
{
BackgroundJob.Enqueue(() => webServiceCall(no, tokenSource));//loop and give jobs to hangfire
}
WebServiceCall implementation
public async Task webServiceCall(int no, CancellationTokenSource tokenSource)
{
using (HttpClient client = new HttpClient())
{
try
{
var result = await client.GetAsync("https://localhost:44359/api/process/" + no, tokenSource.Token);
if (result.IsSuccessStatusCode)
{
var val = await result.Content.ReadAsStringAsync();
System.Diagnostics.Debug.WriteLine("Returned Value : " + val);
//logic code
}
}
catch (TaskCanceledException ex)
{
System.Diagnostics.Debug.WriteLine("HTTP Get request canceled." + ex.Message);
}
}
}
This code works and I need to cancel above batch from separate API. I try something like this and it doesn't work.
[HttpGet("CancellingJob")]
public async Task HagfireCancel()
{
tokenSource.Cancel();
}
But when I call to this, all processes happens as usual and did not canceled.... Can any one help me to cancel hangfire jobs?
I used Hangfire 1.7.0 version.

HttpClient.GetAsync fails after running for some time with TaskCanceledException "A task was cancelled" [duplicate]

It works fine when have one or two tasks however throws an error "A task was cancelled" when we have more than one task listed.
List<Task> allTasks = new List<Task>();
allTasks.Add(....);
allTasks.Add(....);
Task.WaitAll(allTasks.ToArray(), configuration.CancellationToken);
private static Task<T> HttpClientSendAsync<T>(string url, object data, HttpMethod method, string contentType, CancellationToken token)
{
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(method, url);
HttpClient httpClient = new HttpClient();
httpClient.Timeout = new TimeSpan(Constants.TimeOut);
if (data != null)
{
byte[] byteArray = Encoding.ASCII.GetBytes(Helper.ToJSON(data));
MemoryStream memoryStream = new MemoryStream(byteArray);
httpRequestMessage.Content = new StringContent(new StreamReader(memoryStream).ReadToEnd(), Encoding.UTF8, contentType);
}
return httpClient.SendAsync(httpRequestMessage).ContinueWith(task =>
{
var response = task.Result;
return response.Content.ReadAsStringAsync().ContinueWith(stringTask =>
{
var json = stringTask.Result;
return Helper.FromJSON<T>(json);
});
}).Unwrap();
}
There's 2 likely reasons that a TaskCanceledException would be thrown:
Something called Cancel() on the CancellationTokenSource associated with the cancellation token before the task completed.
The request timed out, i.e. didn't complete within the timespan you specified on HttpClient.Timeout.
My guess is it was a timeout. (If it was an explicit cancellation, you probably would have figured that out.) You can be more certain by inspecting the exception:
try
{
var response = task.Result;
}
catch (TaskCanceledException ex)
{
// Check ex.CancellationToken.IsCancellationRequested here.
// If false, it's pretty safe to assume it was a timeout.
}
I ran into this issue because my Main() method wasn't waiting for the task to complete before returning, so the Task<HttpResponseMessage> was being cancelled when my console program exited.
C# ≥ 7.1
You can make the main method asynchronous and await the task.
public static async Task Main(){
Task<HttpResponseMessage> myTask = sendRequest(); // however you create the Task
HttpResponseMessage response = await myTask;
// process the response
}
C# < 7.1
The solution was to call myTask.GetAwaiter().GetResult() in Main() (from this answer).
var clientHttp = new HttpClient();
clientHttp.Timeout = TimeSpan.FromMinutes(30);
The above is the best approach for waiting on a large request.
You are confused about 30 minutes; it's random time and you can give any time that you want.
In other words, request will not wait for 30 minutes if they get results before 30 minutes.
30 min means request processing time is 30 min.
When we occurred error "Task was cancelled", or large data request requirements.
Another possibility is that the result is not awaited on the client side. This can happen if any one method on the call stack does not use the await keyword to wait for the call to be completed.
Promoting #JobaDiniz's comment to an answer:
Do not do the obvious thing and dispose the HttpClient instance, even though the code "looks right":
async Task<HttpResponseMessage> Method() {
using (var client = new HttpClient())
return client.GetAsync(request);
}
Disposing the HttpClient instance can cause following HTTP requests started by other instances of HttpClient to be cancelled!
The same happens with C#'s new RIAA syntax; slightly less obvious:
async Task<HttpResponseMessage> Method() {
using var client = new HttpClient();
return client.GetAsync(request);
}
Instead, the correct approach is to cache a static instance of HttpClient for your app or library, and reuse it:
static HttpClient client = new HttpClient();
async Task<HttpResponseMessage> Method() {
return client.GetAsync(request);
}
(The Async() request methods are all thread safe.)
in my .net core 3.1 applications I am getting two problem where inner cause was timeout exception.
1, one is i am getting aggregate exception and in it's inner exception was timeout exception
2, other case was Task canceled exception
My solution is
catch (Exception ex)
{
if (ex.InnerException is TimeoutException)
{
ex = ex.InnerException;
}
else if (ex is TaskCanceledException)
{
if ((ex as TaskCanceledException).CancellationToken == null || (ex as TaskCanceledException).CancellationToken.IsCancellationRequested == false)
{
ex = new TimeoutException("Timeout occurred");
}
}
Logger.Fatal(string.Format("Exception at calling {0} :{1}", url, ex.Message), ex);
}
In my situation, the controller method was not made as async and the method called inside the controller method was async.
So I guess its important to use async/await all the way to top level to avoid issues like these.
I was using a simple call instead of async. As soon I added await and made method async it started working fine.
public async Task<T> ExecuteScalarAsync<T>(string query, object parameter = null, CommandType commandType = CommandType.Text) where T : IConvertible
{
using (IDbConnection db = new SqlConnection(_con))
{
return await db.ExecuteScalarAsync<T>(query, parameter, null, null, commandType);
}
}
Another reason can be that if you are running the service (API) and put a breakpoint in the service (and your code is stuck at some breakpoint (e.g Visual Studio solution is showing Debugging instead of Running)). and then hitting the API from the client code. So if the service code a paused on some breakpoint, you just hit F5 in VS.

How to cancel .Net Core Web API request using Angular?

I have the following two applications
Angular 6/7 App
.Net Core Web API
I am making GET request to API using Angular's HttpClient as shown below
this.subscription = this.httpClient.get('api/Controller/LongRunningProcess')
.subscribe((response) =>
{
// Handling response
});
API controller's LongRunningProcess method has the following code
[HttpGet]
[Route("LongRunningProcess")]
public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
{
try
{
// Dummy long operation
await Task.Factory.StartNew(() =>
{
for (int i = 0; i < 10; i++)
{
// Option 1 (Not working)
if (cancellationToken.IsCancellationRequested)
break;
// Option 2 (Not working)
cancellationToken.ThrowIfCancellationRequested();
Thread.Sleep(6000);
}
}, cancellationToken);
}
catch (OperationCanceledException e)
{
Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {e.Message}");
}
return Ok();
}
Now I want to cancel this long-running process so I am unsubscribing from client side as shown below
// On cancel button's click
this.subscription.unsubscribe();
Above code will cancel the request and I can see it is canceled in the Network tab of the browser as shown below
But it is not going to make IsCancellationRequested to true in the method LongRunningProcess of the API, so the operation will keep going.
[Note]: Both Option 1 and Option 2 in API method are not working even if I make a call using postman.
Question: Is there any way to cancel that LongRunningProcess method's operation?
When angular cancel request, you can get cancellation token from http context
CancellationToken cancellationToken = HttpContext.RequestAborted;
if (cancellationToken.IsCancellationRequested)
{
// The client has aborted the request
}
You dont need break in this case only use like this
[HttpGet]
[Route("LongRunningProcess")]
public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
{
for (int i = 0; i < 10; i++)
{
cancellationToken.ThrowIfCancellationRequested();
// Dummy long operation
await Task.Factory.StartNew(() => Thread.Sleep(60000));
}
return Ok();
}
You can read it more here
This is because your dummy long operation does not monitor the canncellationToken. I'm not sure it is actually your intention to start 10 one-minute tasks all in parallel without any delay, which is what your code does.
In order to have a dummy long operation, the code would be like
[HttpGet]
[Route("LongRunningProcess")]
public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
{
// Dummy long operation
await Task.Run(() =>
{
for (var i = 0; i < 60; i++)
{
if (cancel.IsCancellationRequested)
break;
Task.Delay(1000).Wait();
}
});
return Ok();
}
Task.Run is just equivalent to Task.Factory.StartNew, by the way.
However, if you just need a dummy long-run operation in your web API, then you can also simply use Task.Delay, which supports cancellation token. Task.Delay throws an exception when the request is canceled, so add exception handling code when you need to do something after request cancellation.
[HttpGet]
[Route("LongRunningProcess")]
public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
{
// Dummy long operation
await Task.Delay(60000, cancel);
return Ok();
}
Any http observables still running at the time will complete and run their logic unless you unsubscribe in onDestroy(). Whether the consequences are trivial or not will depend upon what you do in the subscribe handler. If you try to update something that doesn't exist anymore you may get an error.
Tip: The Subscription contains a closed boolean property that may be useful in advanced cases. For HTTP this will be set when it completes. In Angular it might be useful in some situations to set a _isDestroyed property in ngDestroy which can be checked by your subscribe handler.
Tip 2: If handling multiple subscriptions you can create an ad-hoc new Subscription() object and add(...) any other subscriptions to it - so when you unsubscribe from the main one it will unsubscribe all the added subscriptions too.
So, best practice is to use takeUntil() and unsubscribe from http calls when the component is destroyed.
import { takeUntil } from 'rxjs/operators';
.....
ngOnDestroy(): void {
this.destroy$.next(); // trigger the unsubscribe
this.destroy$.complete(); // finalize & clean up the subject stream
}
var cancellationToken = new CanellationToken();
cancellationToken.CancelAfter(2000);
using (var response = await _httpClient.GetAsync("emp",
HttpCompletionOption.ResponseHeadersRead, cancellationTokenSource.Token))
{
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync();
var emp = await JsonSerializer.DeserializeAsync<List<empDto>>(stream, _options);
}
Further we can also have this "CancellationToken" class, which is nothing much Http client method which terminates the request after certain time-interval.
In angular subscription.unsubscribe(); closes the channel and causes CORE to cancel the API caller's thread, that's good.
Don't use await Task.Run(()... This creates a result/task that should be disposed, if not, the task keeps going, your pattern doesn't permit this - that's why it continues to run.
Simply - 'await this.YourLongRunningFunction()', I'm pretty sure that when the owning thread throws the OperationCancelled exception your task will end.
If "3" doesn't work, then pass a cancellation token to your long running task and set that when you catch your OperationCancelled exception.

How to enable timeout policy with polly

I'm trying out the timeout policy with polly.
static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
timeoutPolicy().GetAwaiter().GetResult();
stopwatch.Stop();
Console.WriteLine("Time elapsed: {0}", stopwatch.Elapsed);
Console.ReadKey();
}
static async Task timeoutPolicy()
{
AsyncTimeoutPolicy<HttpResponseMessage> timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(1); // setup the timeout limit to be 1 sec
HttpResponseMessage response = await timeoutPolicy.ExecuteAsync((ct) => LongOperation(), CancellationToken.None);
}
static Task<HttpResponseMessage> LongOperation()
{
return Task<HttpResponseMessage>.Factory.StartNew(() =>
{
Thread.Sleep(5000); // Sleep 5 seconds
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.BadRequest
};
});
}
I expect an exception to be thrown after 1 sec passed because that is the timeout upper limit I set up. But currently, no exception will be thrown and the method LongOperation() returns normally after around 5 secs.
Why does the timeout policy not work in this case?
Why does the timeout policy not work in this case?
Polly's TimeoutPolicy exists in two modes:
TimeoutStrategy.Optimistic expects governed delegates to respond to co-operative cancellation by CancellationToken.
TimeoutStrategy.Pessimistic allows the calling code to walk away from waiting for a delegate that doesn't respond to co-operative cancellation.
Optimistic mode is the default, so your posted code uses this. But (a) LongOperation() in the posted code does not respond to co-operative cancellation; so the policy does not time it out.
Pessimistic mode with asynchronous policies is intentionally designed only to govern delegates which conform to the normal async pattern. Thread.Sleep() in the posted LongOperation() is fully sychronous; so your example would additionally not be timed out just by switching to TimeoutStrategy.Pessimistic.
TimeoutStrategy.Optimistic is the best simulation of calls through HttpClient, as those calls do respond to CancellationToken.
Async timeout policy's optimistic mode timing out a long operation can be simulated with await Task.Delay(...) honouring a CancellationToken, like this:
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Polly;
using Polly.Timeout;
public class Program
{
public static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
try {
timeoutPolicy().GetAwaiter().GetResult();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
stopwatch.Stop();
Console.WriteLine("Time elapsed: {0}", stopwatch.Elapsed);
}
static async Task timeoutPolicy()
{
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(1); // setup the timeout limit to be 1 sec
HttpResponseMessage response = await timeoutPolicy.ExecuteAsync((ct) => LongOperation(ct), CancellationToken.None);
}
static async Task<HttpResponseMessage> LongOperation(CancellationToken token)
{
await Task.Delay(5000, token);
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.BadRequest
};
}
}
Right now you're using Optimistic timeout which expects the delgate you're calling to respect the and respond to the cancellation token. Your delegate does not, in this case you need to use Pessimistic timeout to ensure your caller doesn't wait passed the defined timeout.
static async Task timeoutPolicy()
{
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(1, TimeoutStrategy.Pessimistic); // setup the timeout limit to be 1 sec
HttpResponseMessage response = await timeoutPolicy.ExecuteAsync((ct) => LongOperation(), CancellationToken.None);
}
Polly Timeout as stated in the docs Optimistic is the default.
As #moutain traveller already pointed out, optimistic timeout requires you to to pass and co-operate with the timeout cancellation. This is handled already by api calls using the HttpClient but in this sample case your code would look something like this:
static async Task timeoutPolicy()
{
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(1, TimeoutStrategy.Optimistic); // setup the timeout limit to be 1 sec
HttpResponseMessage response = await timeoutPolicy.ExecuteAsync((ct) => LongOperation(ct), CancellationToken.None);
}
static Task<HttpResponseMessage> LongOperation(CancellationToken token)
{
return Task<HttpResponseMessage>.Factory.StartNew(() =>
{
var longTask = Task.Delay(5000);
while (!longTask.IsCompleted)
{
token.ThrowIfCancellationRequested();
}
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.BadRequest
};
});
}

Categories

Resources