Any way to differentiate Cancel and Timeout - c#

I have some code that is validating some data by making calls to a number of other services. I start all of the calls in parallel and then wait until at least one of them finishes. If any of the requests fail, I don't care about the result of the other calls.
I make the calls with HttpClient and I have passed an HttpMessageHandler in that does a bunch of logging. Essentially:
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = null;
try
{
response = await base.SendAsync(request, cancellationToken);
}
catch (OperationCanceledException ex)
{
LogTimeout(...);
throw;
}
catch (Exception ex)
{
LogFailure(...);
throw;
}
finally
{
LogComplete(...);
}
return response;
}
No the part that I'm having trouble with is when I cancel the requests. When I cancel a request, I'm doing it on purpose, so I don't want it to get logged as a timeout, but there doesn't appear to be any difference between a cancellation and a real timeout.
Is there anyway to accomplish this?
Edit: I need to clarify this, a little bit. The service making the calls in parallel is passing in CancellationTokens with a timeout:
var ct = new CancellationTokenSource(TimeSpan.FromSeconds(2));
So when the server takes more than two seconds to respond, I get an OperationCanceledException, and if I manually cancel the token source (say because another server returned an error after 1 second), then I still get an OperationCanceledException. Ideally, I would be able to look at CancellationToken.IsCancellationRequested to determine if it was cancelled due to a timeout, as opposed to explicitly requested to be cancelled, but it appears that you get the same value regardless of how it was canceled.

If you want to distinguish the two cancellation types, then you need to use two different cancellation tokens. There's no other way. This is not too hard since they can be linked - just a bit awkward.
The cleanest way to write this IMO is to move the timeout code into the SendAsync method instead of the calling method:
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
cts.CancelAfter(TimeSpan.FromSeconds(2));
try
{
return await base.SendAsync(request, cts.Token);
}
catch (OperationCanceledException ex)
{
if (cancellationToken.IsCancellationRequested)
return null;
LogTimeout(...);
throw;
}
catch (Exception ex)
{
LogFailure(...);
throw;
}
finally
{
LogComplete(...);
}
}
}
If you don't want to move the timeout code into SendAsync, then you'll need to do the logging outside of that method, too.

If the exceptions aren't telling you the difference between the two cases then you will need to check with either the Task or the CancellationToken to see if there was actually a cancellation.
I would lean toward asking the Task which will have its IsCanceled property return true if an unhandled OperationCanceledException was thrown (using CancellationToken.ThrowIfCancellationRequested inside base.SendAsync most likely). Something like this...
HttpResponseMessage response = null;
Task sendTask = null;
try
{
sendTask = base.SendAsync(request, cancellationToken);
await sendTask;
}
catch (OperationCanceledException ex)
{
if (!sendTask.IsCancelled)
{
LogTimeout(...);
throw;
}
}
EDIT
In response to the update to the question, I wanted to update my answer. You are right cancellation whether it is specifically requested on the CancellationTokenSource or if it is caused by a timeout will lead to exactly the same outcome. If you decompile CancellationTokenSource you will see that for the timeout it just sets a Timer callback that will explicitly call CancellationTokenSource.Cancel when the timeout is reached, so both ways will end up calling the same Cancel method.
I think if you want to tell the difference you will need to derive from CancellationTokenSource (it isn't a sealed class) and then add your own custom cancel method that will set a flag to let you know that you explicitly cancelled the operation rather than letting it time out.
This is unfortunate since you will have both your custom cancel method and the original Cancel method available and will have to be sure to use the custom one. You may be able to get away with your custom logic just hiding the existing Cancel operation with something like this:
class CustomCancellationTokenSource : CancellationTokenSource
{
public bool WasManuallyCancelled {get; private set;}
public new void Cancel()
{
WasManuallyCancelled = true;
base.Cancel();
}
}
I would think that hiding the base method will work, you can give it a shot and find out.

Related

Cancellable Async Web Request

I am trying to make a generic method that will cancel an Async web request and any additional operations associated with it. I found another question regarding this such as this.
And I have written a helper class that will do just this. I present it below:
public static class Helpers
{
public static async Task<string> GetJson(string url,
CancellationTokenSource cancellationTokenSource,
bool useSynchronizationContext = true)
{
try
{
var request = (HttpWebRequest)WebRequest.Create(url);
string jsonStringResult;
using (WebResponse response = await request.GetResponseAsync()
.WithCancellation(cancellationTokenSource.Token,
request.Abort, useSynchronizationContext))
{
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
jsonStringResult = await reader.ReadToEndAsync();
reader.Close();
dataStream.Close();
}
cancellationTokenSource.Token.ThrowIfCancellationRequested();
return jsonStringResult;
}
catch (Exception ex) when (ex is OperationCanceledException
|| ex is TaskCanceledException)
{
}
catch (Exception ex) when (ex is WebException
&& ((WebException)ex).Status == WebExceptionStatus.RequestCanceled)
{
}
catch (Exception ex)
{
//Any other exception
}
finally
{
cancellationTokenSource.Dispose();
}
return default;
}
public static async Task<T> WithCancellation<T>(this Task<T> task,
CancellationToken cancellationToken, Action action,
bool useSynchronizationContext)
{
using (cancellationToken.Register(action, useSynchronizationContext))
{
return await task;
}
}
}
Notice the line
cancellationTokenSource.Token.ThrowIfCancellationRequested();
right before returning the JSON string.
When the operation is canceled and the flow of execution is on line
StreamReader reader = new StreamReader(dataStream);
for example, all lines below (reader.Close() etc.) will be executed and the exception will be thrown when ThrowIfCancelationRequested() is executed - is that correct? Am I missing something?
If so, is there a way to cancel everything at once?
Thanks everyone for their response,
After the answer provided and all really useful comments I updated the implementation.I used HttpClient and the extension method in the link for having a task that cannot actually being canceled to behave like one that can - readAsStringAsync is actually executed since it cannot accept a cancellation token.
public static class Helpers
{
public static async Task<string> GetJson(string url,CancellationToken cancellationToken)
{
try
{
string jsonStringResult;
using (var client = new HttpClient())
{
cancellationToken.ThrowIfCancellationRequested();
using (var response = await client.GetAsync(url, cancellationToken))
{
jsonStringResult = await response.Content.ReadAsStringAsync().WithCancellation(cancellationToken);
}
}
return jsonStringResult;
}
catch (Exception ex) when (ex is OperationCanceledException)
{
}
catch (Exception ex) when (ex is WebException exception && exception.Status == WebExceptionStatus.RequestCanceled)
{
}
catch (Exception ex)
{
//LogException(ex);
}
return default;
}
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);
}
}
all lines below (reader.Close() etc.) will be executed and the
exception will be thrown when ThrowIfCancelationRequested() is
executed - is that correct? Am I missing something?
If so, is there a way to cancel everything at once?
First of all, operation that you want to cancel has to explicitly support being canceled. So, you have to push your best to use a code for executing some operation in a way which somehow takes CancellationToken as an argument. If it is not possible, then you have no other chance.
That's why this concept is called Cooperative cancellation. Because almost always both sides should be aware of that cancellation happened. Client-side should be aware of that the code was actually canceled, it’s not enough for a client to know that cancellation was just requested. For the callee, it’s important to know about the fact that cancellation was requested in order to properly finish itself.
Regarding to checking whether operation is cancelled while executing Close method of stream and reader. You have to call cleanup methods always whether operation is cancelled or not if you want to avoid memory leaks. Of course I suggest you to use using statement which will automatically do that cleanup.
And by the way, for making some function cancelable you don't have to check whether cancellation is requested before executing each line. You just have to check whether cancellation is request before and after executing some long-running operation. And pass cancellation token if that long-running operations supports cancelling through cancellation token property.
Also, you have to take a look for side-effects. Don’t cancel if you’ve already incurred side-effects that your method isn’t prepared to revert on the way out that would leave you in an inconsistent state.
Some general code-block might be so:
if(ct.IsCancellationRequested)
{
break; // or throw
}
await DoSomething(ct);
if (ct.IsCancellationRequested)
{
// if there is no side-effect
return; // or throw
// or, we already did something in `DoSomething` method
// do some rollback
}
As a solution, you can use some different objects like HttpClient or WebRequest for executing async, awaitable and cancelable web request. You can take a look to that link for implementation details.

Is there a way to "force delete" an instance from memory if it is using a while loop?

Assume ClassX has a forever while loop.
Is there a way to "force stop/delete/clear memory" of a ClassX instance?
or do I have to stop the while loop manually within the class for GC?
The bulk of your question seems to be asking how to exit out of a continuous loop in an asynchronous context. The solution to your problem is cancellation tokens.
A simple example might be the following:
class Caller
{
private CancellationTokenSource _cancellationTokenSource;
private Runner _runner;
public async Task StartAsync()
{
_cancellationTokenSource = new CancellationTokenSource();
try
{
_runner = new Runner();
await _runner.DoWorkAsync(_cancellationTokenSource.Token);
}
catch (OperationCanceledException) when (_cancellationTokenSource.IsCancellationRequested)
{
// this will ignore the exception when cancellation is requested. it might be useful in a reset scenario.
// otherwise, don't try/catch and just let the exception bubble up
}
finally
{
_runner = null;
_cancellationTokenSource = null;
}
}
public void Stop()
{
_cancellationTokenSource?.Cancel();
}
}
class Runner
{
public async Task DoWorkAsync(CancellationToken cancellationToken)
{
while(true)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(TimeSpan.FromSeconds(30), cancellationToken);
}
}
}
The caller's StartAsync() will create a CancellationTokenSource, and start the DoWorkAsync() method of the runner by passing in the cancellation token.
The caller's DoWorkAsync() method will, on every iteration of the loop, ask the token to throw an exception if the source has requested cancellation. Note that cancellation is cooperative - you have to check if cancellation has been requested for it to work. You should also pass the cancellationToken into any async methods that accept it, as I've demonstrated with Task.Delay.
Note that I've used catch (OperationCanceledException) when (_cancellationTokenSource.IsCancellationRequested) in the StartAsync() method. Since this is a reset scenario, I don't want the exception to bubble up and crash the application, but I don't want to catch similar exceptions caused by something other than our cancellation request, hence the when .... part. How you want to handle these things depends on you, I just figured I'd demonstrate this at the same time in case it's useful in your scenario.
Now, when we call Stop(), it simply flags the cancellation request on the token, so that our co-operative cancellation code will know it's time to cancel the execution.

Best way to return from an async void method if an awaited task is canceled [duplicate]

In our application we work a lot with async / await and Tasks. Therefore it does use Task.Run a lot, sometimes with cancellation support using the built in CancellationToken.
public Task DoSomethingAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
while (true)
{
if (cancellationToken.IsCancellationRequested) break;
//do some work
}
}, cancellationToken);
}
If i do now cancel the execution using the CancellationToken the execution does stop at the beginning of the next loop, or if the Task did not start at all it throws an exception (TaskCanceledException inside Task.Run). The question is now why does Task.Run use an Exception to control successful cancellation instead of just returning a completed Task. Is there any specific reason MS did not stick to the "Do NOT use exceptions to control execution flow" rule?.
And how can i avoid Boxing every method that supports cancellation (which are a lot) in an completely useless try catch (TaskCancelledException) block?
Well, you can't really see the difference in your very simple scenario - you're not actually using the result of the Task, and you don't need to propagate the cancellation through a complex call stack.
First, your Task might return a value. What do you return when the operation was cancelled?
Second, there may be other tasks that follow your cancelled task. You probably want to propagate the cancellation through the other tasks at your convenience.
Exceptions propagate. Task cancellation is pretty much identical to Thread.Abort in this usage - when you issue a Thread.Abort, a ThreadAbortException is used to make sure you unwind all the way back to the top. Otherwise, all of your methods would have to check the result of every method they call, check if they were cancelled, and return themselves if needed - and we've already seen that people will ignore error return values in old-school C :)
In the end, task cancellation, just like thread aborts, is an exceptional scenario. It already involves synchronization, stack unwinding etc.
However, this doesn't mean you necessarily have to use try-catch to catch the exception - you can use task states. For example, you can use a helper function like this:
public static Task<T> DefaultIfCanceled<T>(this Task<T> #this, T defaultValue = default(T))
{
return
#this.ContinueWith
(
t =>
{
if (t.IsCanceled) return defaultValue;
return t.Result;
}
);
}
Which you can use as
await SomeAsync().DefaultIfCanceled();
Of course, it should be noted that noöne is forcing you to use this method of cancellation - it's simply provided as a convenience. For example, you could use your own amplified type to preserve the cancellation information, and handle the cancellation manually. But when you start doing that, you'll find the reason why cancellation is handled using exceptions - doing this in imperative code is a pain, so you'll either waste a lot of effort for no gain, or you'll switch to a more functional way of programming (come, we have cookies!*).
(*) Disclaimer: We don't actually have cookies. But you can make your own!
The exception is thrown for a purpose as others in the community have already pointed it out.
However, if you would like to have more control over TaskCanceledException behaviour and still have the logic isolated to one place you may implement an Extension method to extend Task which handles cancellation, something like this -
public async Task DoSomethingAsync(CancellationToken cancellationToken)
{
await Task.Run(() =>
{
while (true)
{
if (cancellationToken.IsCancellationRequested) break;
//do some work
}
}).
WithCancellation(cancellationToken,false); // pass the cancellation token to extension funciton instead to run
}
static class TaskCacellationHelper
{
private struct Void { } // just to support TaskCompletionSource class.
public static async Task WithCancellation(this Task originalTask, CancellationToken ct, bool suppressCancellationExcetion)
{
// Create a Task that completes when the CancellationToken is canceled
var cancelTask = new TaskCompletionSource<Void>();
// When the CancellationToken is canceled, complete the Task
using (ct.Register(
t => ((TaskCompletionSource<Void>)t).TrySetResult(new Void()), cancelTask))
{
// Create a Task that completes when either the original or
// CancellationToken Task completes
Task any = await Task.WhenAny(originalTask, cancelTask.Task);
// If any Task completes due to CancellationToken, throw OperationCanceledException
if (any == cancelTask.Task)
{
//
if (suppressCancellationExcetion == false)
{
ct.ThrowIfCancellationRequested();
}
else
{
Console.WriteLine("Cancelled but exception supressed");
}
}
}
// await original task. Incase of cancellation your logic will break the while loop
await originalTask;
}
}

TaskCancellationException how to avoid the exception on success control flow?

In our application we work a lot with async / await and Tasks. Therefore it does use Task.Run a lot, sometimes with cancellation support using the built in CancellationToken.
public Task DoSomethingAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
while (true)
{
if (cancellationToken.IsCancellationRequested) break;
//do some work
}
}, cancellationToken);
}
If i do now cancel the execution using the CancellationToken the execution does stop at the beginning of the next loop, or if the Task did not start at all it throws an exception (TaskCanceledException inside Task.Run). The question is now why does Task.Run use an Exception to control successful cancellation instead of just returning a completed Task. Is there any specific reason MS did not stick to the "Do NOT use exceptions to control execution flow" rule?.
And how can i avoid Boxing every method that supports cancellation (which are a lot) in an completely useless try catch (TaskCancelledException) block?
Well, you can't really see the difference in your very simple scenario - you're not actually using the result of the Task, and you don't need to propagate the cancellation through a complex call stack.
First, your Task might return a value. What do you return when the operation was cancelled?
Second, there may be other tasks that follow your cancelled task. You probably want to propagate the cancellation through the other tasks at your convenience.
Exceptions propagate. Task cancellation is pretty much identical to Thread.Abort in this usage - when you issue a Thread.Abort, a ThreadAbortException is used to make sure you unwind all the way back to the top. Otherwise, all of your methods would have to check the result of every method they call, check if they were cancelled, and return themselves if needed - and we've already seen that people will ignore error return values in old-school C :)
In the end, task cancellation, just like thread aborts, is an exceptional scenario. It already involves synchronization, stack unwinding etc.
However, this doesn't mean you necessarily have to use try-catch to catch the exception - you can use task states. For example, you can use a helper function like this:
public static Task<T> DefaultIfCanceled<T>(this Task<T> #this, T defaultValue = default(T))
{
return
#this.ContinueWith
(
t =>
{
if (t.IsCanceled) return defaultValue;
return t.Result;
}
);
}
Which you can use as
await SomeAsync().DefaultIfCanceled();
Of course, it should be noted that noöne is forcing you to use this method of cancellation - it's simply provided as a convenience. For example, you could use your own amplified type to preserve the cancellation information, and handle the cancellation manually. But when you start doing that, you'll find the reason why cancellation is handled using exceptions - doing this in imperative code is a pain, so you'll either waste a lot of effort for no gain, or you'll switch to a more functional way of programming (come, we have cookies!*).
(*) Disclaimer: We don't actually have cookies. But you can make your own!
The exception is thrown for a purpose as others in the community have already pointed it out.
However, if you would like to have more control over TaskCanceledException behaviour and still have the logic isolated to one place you may implement an Extension method to extend Task which handles cancellation, something like this -
public async Task DoSomethingAsync(CancellationToken cancellationToken)
{
await Task.Run(() =>
{
while (true)
{
if (cancellationToken.IsCancellationRequested) break;
//do some work
}
}).
WithCancellation(cancellationToken,false); // pass the cancellation token to extension funciton instead to run
}
static class TaskCacellationHelper
{
private struct Void { } // just to support TaskCompletionSource class.
public static async Task WithCancellation(this Task originalTask, CancellationToken ct, bool suppressCancellationExcetion)
{
// Create a Task that completes when the CancellationToken is canceled
var cancelTask = new TaskCompletionSource<Void>();
// When the CancellationToken is canceled, complete the Task
using (ct.Register(
t => ((TaskCompletionSource<Void>)t).TrySetResult(new Void()), cancelTask))
{
// Create a Task that completes when either the original or
// CancellationToken Task completes
Task any = await Task.WhenAny(originalTask, cancelTask.Task);
// If any Task completes due to CancellationToken, throw OperationCanceledException
if (any == cancelTask.Task)
{
//
if (suppressCancellationExcetion == false)
{
ct.ThrowIfCancellationRequested();
}
else
{
Console.WriteLine("Cancelled but exception supressed");
}
}
}
// await original task. Incase of cancellation your logic will break the while loop
await originalTask;
}
}

Distinguish timeout from user cancellation

HttpClient has a builtin timeout feature (despite being all asynchronous, i.e. timeouts could be considered orthogonal to the http request functionality and thus be handled by generic asynchronous utilities, but that aside) and when the timeout kicks in, it'll throw a TaskCanceledException (wrapped in an AggregateException).
The TCE contains a CancellationToken that equals CancellationToken.None.
Now if I provide HttpClient with a CancellationToken of my own and use that to cancel the operation before it finishes (or times out), I get the exact same TaskCanceledException, again with a CancellationToken.None.
Is there still a way, by looking only at the exception thrown, to figure out whether a timeout canceled the request, without having to make my own CancellationToken accessible to the code that checks the exception?
P.S. Could this be a bug and CancellationToken got somehow wrongly fixed to CancellationToken.None? In the cancelled using custom CancellationToken case, I'd expect TaskCanceledException.CancellationToken to equal that custom token.
Edit
To make the problem a bit more clear, with access to the original CancellationTokenSource, it is easy to distinguish timeout and user cancellation:
origCancellationTokenSource.IsCancellationRequested == true
Getting the CancellationToken from the exception though gives the wrong answer:
((TaskCanceledException) e.InnerException).CancellationToken.IsCancellationRequested == false
Here a minimal example, due to popular demand:
public void foo()
{
makeRequest().ContinueWith(task =>
{
try
{
var result = task.Result;
// do something with the result;
}
catch (Exception e)
{
TaskCanceledException innerException = e.InnerException as TaskCanceledException;
bool timedOut = innerException != null && innerException.CancellationToken.IsCancellationRequested == false;
// Unfortunately, the above .IsCancellationRequested
// is always false, no matter if the request was
// cancelled using CancellationTaskSource.Cancel()
// or if it timed out
}
});
}
public Task<HttpResponseMessage> makeRequest()
{
var cts = new CancellationTokenSource();
HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) };
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "url");
passCancellationTokenToOtherPartOfTheCode(cts);
return client.SendAsync(httpRequestMessage, cts.Token);
}
The accepted answer is certainly how this should work in theory, but unfortunately in practice IsCancellationRequested does not (reliably) get set on the token that is attached to the exception:
Cancelling an HttpClient Request - Why is TaskCanceledException.CancellationToken.IsCancellationRequested false?
Yes, they both return the same exception (possibly because of timeout internally using a token too) but it can be easily figured out by doing this:
catch (OperationCanceledException ex)
{
if (token.IsCancellationRequested)
{
return -1;
}
return -2;
}
so basically if you hit the exception but your token is not cancelled, well it was a regular http timeout

Categories

Resources