TaskCancellationException how to avoid the exception on success control flow? - c#

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;
}
}

Related

Is there a ContinueWith for ValueTask?

If an API returns a ValueTask or ValueTask<T>, is there a way to perform a ContinueWith on it, like I'm able to do with Task? Is there a Microsoft-provided NuGet library for doing so with .NET Standard 2.0?
Use valueTask.AsTask(). AsTask() is an escape hatch for just such use cases as yours. The purpose of ValueTask is to avoid allocations whenever possible, but if you're going to invoke a continuation on another thread, you will need to allocate something and it might as well be a Task<T>.
Here you are:
public static async ValueTask ContinueWith<TResult>(this ValueTask<TResult> source,
Action<ValueTask<TResult>> continuationAction)
{
// The source task is consumed after the await, and cannot be used further.
ValueTask<TResult> completed;
try
{
completed = new(await source.ConfigureAwait(false));
}
catch (OperationCanceledException oce)
{
var tcs = new TaskCompletionSource<TResult>();
tcs.SetCanceled(oce.CancellationToken);
completed = new(tcs.Task);
}
catch (Exception ex)
{
completed = new(Task.FromException<TResult>(ex));
}
continuationAction(completed);
}
A simpler implementation would be to Preserve the ValueTask<TResult> before the await, and then invoke the continuationAction passing the preserved task as argument. This would cause invariably an allocation though, even in the case of the successful completion of the task.
The reason that I am avoiding the one-line Task.FromCanceled method in the cancellation case, is because this method requires that the supplied CancellationToken is canceled, otherwise it throws an ArgumentOutOfRangeException.
I know I am posting it too late. However wanted to share the approach I used...
ValueTask<T> vt = ValueTaskTReturningMethod();
var awt = vt.GetAwaiter();
awt.OnCompleted(() => {
T val = awt.GetResult();
//code you want to execute after async operation completes
}

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;
}
}

How to safely cancel a task using a CancellationToken and await Task.WhenAll

I have a framework which creates a CancellationTokenSource, configures CancelAfter, then calls an async method and passes the Token. The async method then spawns many tasks, passing the cancellation token to each of them, and then awaits the collection of tasks. These tasks each contain logic to gracefully cancel by polling IsCancellationRequested.
My issue is that if I pass the CancellationToken into Task.Run() an AggregateException is thrown containing a TaskCanceledException. This prevents the tasks from gracefully canceling.
To get around this I can not pass the CancelationToken into Task.Run, however I'm not sure what I will be losing. For instance, I like the idea that if my task hangs and cannot perform the graceful cancel this exception will force it down. I was thinking I could string along two CancelationTokens to handle this, one 'graceful' and the other 'force'. However, I don't like that solution.
Here is some psudo-code representing what I described above..
public async Task Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(30000);
await this.Run(cts.Token);
}
public async Task Run(CancellationToken cancelationToken)
{
HashSet<Task> tasks = new HashSet<Task>();
foreach (var work in this.GetWorkNotPictured)
{
// Here is where I could pass the Token,
// however If I do I cannot cancel gracefully
// My dilemma here is by not passing I lose the ability to force
// down the thread (via exception) if
// it's hung for whatever reason
tasks.Add(Task.Run(() => this.DoWork(work, cancelationToken))
}
await Task.WhenAll(tasks);
// Clean up regardless of if we canceled
this.CleanUpAfterWork();
// It is now safe to throw as we have gracefully canceled
cancelationToken.ThrowIfCancellationRequested();
}
public static void DoWork(work, cancelationToken)
{
while (work.IsWorking)
{
if (cancelationToken.IsCancellationRequested)
return // cancel gracefully
work.DoNextWork();
}
}
I recommend that you follow the standard cancellation pattern of throwing an exception rather than just returning:
public static void DoWork(work, cancellationToken)
{
while (work.IsWorking)
{
cancellationToken.ThrowIfCancellationRequested();
work.DoNextWork();
}
}
If you have cleanup work to do, that's what finally is for (or using, if you can refactor that way):
public async Task Run(CancellationToken cancellationToken)
{
HashSet<Task> tasks = new HashSet<Task>();
foreach (var work in this.GetWorkNotPictured)
{
tasks.Add(Task.Run(() => this.DoWork(work, cancellationToken))
}
try
{
await Task.WhenAll(tasks);
}
finally
{
this.CleanUpAfterWork();
}
}
Provide the CancellationToken to Task.Run in addition to passing it to the method doing the work. When you do this Task.Run can see that the exception thrown was caused by the CancellationToken it was given, and will mark the Task as cancelled.
tasks.Add(Task.Run(() => this.DoWork(work, cancelationToken),
cancelationToken));
Once you've done this you can ensure that DoWork throws when the token is cancelled, rather than checking IsCancellationRequested to try to end by being marked as "completed successfully".

Chaining Parallel Tasks to an End Condition or Error Condition

I'm trying to wrap my head around some of the syntax and structure for parallel tasks in C#, specifically around chaining multiple tasks and handling errors.
The specific sequence of steps I'm looking to create are:
Spawn a parallel task and immediately return to the UI.
In the parallel task:
Do Process1()
If Process1() completes without error, do Process2()
If Process2() completes without error, do Process3()
If all tasks complete without error, do SuccessCondition()
If any task resulted in an error, do ErrorCondition()
It's my understanding that I would create a Task and call ContinueWith() to chain more tasks, passing in a TaskContinuationOptions flag to determine when to do that continuation. Additionally, that success/error conditions would fall through all of the continuations to the end. So I'm currently trying this:
var task = new Task(() => Process1());
var task2 = task.ContinueWith(t => Process2(), TaskContinuationOptions.OnlyOnRanToCompletion);
var task3 = task2.ContinueWith(t => Process3(), TaskContinuationOptions.OnlyOnRanToCompletion);
var task4 = task3.ContinueWith(t => SuccessCondition(t), TaskContinuationOptions.OnlyOnRanToCompletion);
var task5 = task4.ContinueWith(t => ErrorCondition(t), TaskContinuationOptions.NotOnRanToCompletion);
task.Start();
It appears to be behaving as expected, except that within ErrorCondition() the instance of t doesn't appear to have an exception, even if I manually threw one from within, say, Process2(). Looking at the MSDN article for handling exceptions, it says to do this:
try
{
task.Wait();
}
catch (AggregateException ex)
{
// Handle exceptions from a collection on ex
}
However, I tried that and it doesn't seem to have the exceptions there either. Also, by calling .Wait() in the main thread like that, am I negating the parallelism and just blocking? It appears that way in my tests.
So I guess my question is... What's the correct way to chain dependent tasks and handle an overall success/error condition here? Or, how should exceptions thrown from within these tasks be properly caught, while still returning immediately to the UI?
Note that if you're using .NET 4.5 and can use async/await, you can do this much more cleanly:
public async Task DoProcess()
{
try
{
await Task.Run(Process1);
await Task.Run(Process2);
await Task.Run(Process3);
SuccessCondition();
}
catch (Exception ex)
{
ErrorCondition(ex);
}
}
If you launch this from the UI thread, SuccessCondition and ErrorCondition will occur on the UI Thread as well. One functional difference here is that the exception you catch will not be an AggregateException; it will instead be the actual exception thrown during the awaited call that failed.
You would need to add a continuation to all of the tasks, 1-4, with the error handling case to allow an error in any of them to call that function.
For convenience you could create a method to add the same continuation to a collection of tasks. Here's one (feel free to add others for the other overloads of ContinueWith as needed):
public static IEnumerable<Task> ContinueWith(this IEnumerable<Task> tasks
, Action<Task> continuation, TaskContinuationOptions options)
{
return tasks.Select(task => task.ContinueWith(continuation, options))
.ToList();//important for this ToList to be here;
//we want the continuations to be added now, not when the result is iterated
}
This allows you to write:
var errorTasks = new[]{task, task2, task3, task4}
.ContinueWith(ErrorCondition, TaskContinuationOptions.NotOnRanToCompletion);
Error handling for tasks is made so much easier in C# 5.0 with async methods though. It would allow you to transform your code into this:
public static async Task Foo()
{
try
{
await Task.Run(Process1())
await Task.Run(Process2())
await Task.Run(Process3())
SuccessCondition();
}
catch (SomeExceptionType ex)
{
HandleException(ex);
}
}
This functions just as you would think it does as per your requirements, which is awesome.

Categories

Resources