Why does TaskCanceledException occur? - c#

I have the following test code:
void Button_Click(object sender, RoutedEventArgs e)
{
var source = new CancellationTokenSource();
var tsk1 = new Task(() => Thread1(source.Token), source.Token);
var tsk2 = new Task(() => Thread2(source.Token), source.Token);
tsk1.Start();
tsk2.Start();
source.Cancel();
try
{
Task.WaitAll(new[] {tsk1, tsk2});
}
catch (Exception ex)
{
// here exception is caught
}
}
void Thread1(CancellationToken token)
{
Thread.Sleep(2000);
// If the following line is enabled, the result is the same.
// token.ThrowIfCancellationRequested();
}
void Thread2(CancellationToken token)
{
Thread.Sleep(3000);
}
In the thread methods I don't throw any exceptions, but I get TaskCanceledException in try-catch block of the outer code which starts the tasks. Why this happens and what is the purpose of token.ThrowIfCancellationRequested(); in this case. I believe the exception should only be thrown if I call token.ThrowIfCancellationRequested(); in the thread method.

I believe this is expected behavior because you're running in to a variation of a race condition.
From How to: Cancel a task and its children:
The calling thread does not forcibly end the task; it only signals that cancellation is requested. If the task is already running, it is up to the user delegate to notice the request and respond appropriately. If cancellation is requested before the task runs, then the user delegate is never executed and the task object transitions into the Canceled state.
and from Task Cancellation:
You can terminate the operation by [...] simply returning from the delegate. In many scenarios this is sufficient; however, a task instance that is "canceled" in this way transitions to the RanToCompletion state, not to the Canceled state.
My educated guess here is that while you are calling .Start() on your two tasks, chances are that one (or both of them) didn't actually start before you called .Cancel() on your CancellationTokenSource. I bet if you put in at least a three second wait between the start of the tasks and the cancellation, it won't throw the exception. Also, you can check the .Status property of both tasks. If I'm right, the .Status property should read TaskStatus.Canceled on at least one of them when the exception is thrown.
Remember, starting a new Task does not guarantee a new thread being created. It falls to the TPL to decide what gets a new thread and what is simply queued for execution.

Related

Using Task.Delay within Task.Run

I have a Windows Service that monitors my application by running a couple of tests every second. A bug report has been submitted that said that the service stoppes running after a while, and I'm trying to figure out why.
I suspect that the code below is the culprit, but I have trouble understanding exactly how it works. The ContinueWith statement has recently been commented out, but I dont know if it is needed
private Task CreateTask(Action action)
{
var ct = _cts.Token;
return Task.Run(async () =>
{
ct.ThrowIfCancellationRequested();
var sw = new Stopwatch();
while (true)
{
sw.Restart();
action();
if (ct.IsCancellationRequested)
{
_logger.Debug("Cancellation requested");
break;
}
var wait = _settings.loopStepFrequency - sw.ElapsedMilliseconds;
if (wait <= 0) // No need to delay
continue;
// If ContinueWith is needed wrap this in an ugly try/catch
// handling the exception
await Task.Delay(
(int)(_settings.loopStepFrequency - sw.ElapsedMilliseconds),
ct); //.ContinueWith(tsk => { }, ct);
}
_logger.Debug("Task was cancelled");
}, _cts.Token);
}
Are there any obvious problems with this code?
Are there any obvious problems with this code?
The one that jumps out to me is the calculation for the number of milliseconds to delay. Specifically, there's no floor. If action() takes an unusually long time, then the task could fail in a possibly unexpected way.
There are several ways for the task to complete in either a cancelled or failed state, or it can delay forever:
The task can be cancelled before the delegate begins, due to the cancellation token passed to Task.Run.
The task can be cancelled by the ThrowIfCancellationRequested call.
The task can complete successfully after being cancelled, due to the IsCancellationRequested logic.
The task can be cancelled by the cancellation token passed to Task.Delay.
The task may fail with an ArgumentOutOfRangeException if _settings.loopStepFrequency - sw.ElapsedMilliseconds is less than -1. This is probably a bug.
The task may delay indefinitely (until cancelled) if _settings.loopStepFrequency - sw.ElapsedMilliseconds happens to be exactly -1. This is probably a bug.
To fix this code, I recommend two things:
The code is probably intending to do await Task.Delay((int) wait, ct); instead of await Task.Delay((int)(_settings.loopStepFrequency - sw.ElapsedMilliseconds), ct);. This will remove the last two conditions above.
Choose one method of cancellation. The standard pattern to express cancellation is via OperationCanceledExcpetion; this is the pattern used by ThrowIfCancellationRequested and by Task.Delay. The IsCancellationRequested check is using a different pattern; it will successfully complete the task on cancellation, instead of cancelling it.
There are so many problems with this code, that makes more sense to rewrite it than attempt to fix it. Here is a possible way to rewrite this method, with some (possibly superfluous) argument validation added:
private Task CreateTask(Action action)
{
if (action == null) throw new ArgumentNullException(nameof(action));
var ct = _cts.Token;
var delayMsec = _settings.loopStepFrequency;
if (delayMsec <= 0) throw new ArgumentOutOfRangeException("loopStepFrequency");
return Task.Run(async () =>
{
while (true)
{
var delayTask = Task.Delay(delayMsec, ct);
action();
await delayTask;
}
}, ct);
}
The responsibility for logging a possible exception/cancellation belongs now to the caller of the method, that (hopefully) awaits the created task.
var task = CreateTask(TheAction);
try
{
await task; // If the caller is async
//task.GetAwaiter().GetResult(); // If the caller is sync
_logger.Info("The task completed successfully");
}
catch (OperationCanceledException)
{
_logger.Info("The task was canceled");
}
catch (Exception ex)
{
_logger.Error("The task failed", ex);
}

Cancelling multiple tasks by registering callbacks on cancellation tokens

I have the following code piece with the output below.
I was expecting the second task to be cancelled as it also registers a callback on the cancellation token.
But the cancellation only happens on the first task, where the original cancellation was done.
Aren't cancellations supposed to be propagated to all token instances?
The Microsoft article on Cancellation Tokens does not explain this well.
Any pointers on why this is happening?
Code:
class Program
{
static void Main(string[] args)
{
AsyncProgramming();
Console.ReadLine();
}
private static async void AsyncProgramming()
{
try
{
using (var cts = new CancellationTokenSource())
{
var task2 = CreateTask2(cts);
var task1 = CreateTask1(cts);
Thread.Sleep(5000);
await Task.WhenAll(task2, task1);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.WriteLine("Both tasks over");
}
private static async Task CreateTask1(CancellationTokenSource cts)
{
try
{
cts.Token.Register(() => { cts.Token.ThrowIfCancellationRequested(); });
await Task.Delay(5000);
Console.WriteLine("This is task one");
cts.Cancel();
Console.WriteLine("This should not be printed because the task was cancelled");
}
catch (Exception e)
{
Console.WriteLine("Task 1 exception: " + e.Message);
Console.WriteLine("Task 1 was cancelled");
}
}
private static async Task CreateTask2(CancellationTokenSource cts)
{
try
{
cts.Token.Register(() =>
{
Console.WriteLine("Write something");
Thread.CurrentThread.Abort();
cts.Token.ThrowIfCancellationRequested();
});
await Task.Delay(8000);
Console.WriteLine("This is task two");
}
catch (Exception e)
{
Console.WriteLine("Task 2 was cancelled by Task 1");
Console.WriteLine(e);
}
}
}
Output:
This is task one
Write something
Task 1 exception: Thread was being aborted.
Task 1 was cancelled
This is task two
Thread was being aborted.
Both tasks over
The first thing is that when you call CancellationToken.Register all it normally does is to store the delegate to call later.
The thread/logic flow calling CancellationTokenSource.Cancel runs all previously registered delegates, regardless of where those were registered from. This means any exception thrown in those normally does not relate in any way to the methods that called Register.
Side note 1: I said normally above, because there is a case where the call to Register will run the delegate right away. I think this is why the msdn documentation is extra confusing. Specifically: if the token was already cancelled, then Register will run the delegate right away, instead of storing it to be ran later. Underneath that happens in CancellationTokenSource.InternalRegister.
The second thing to complete the picture is that all CancellationToken.ThrowIfCancellationRequested does is to throw an exception wherever it is being ran from. That would normally be wherever CancellationTokenSource.Cancel was called from. Note that normally all registered delegates are ran, even if some of those throw an exception.
Side note 2: throwing ThreadAbortException changes the intended logic in the Cancel method, because that special exception can't be caught. When faced with that, cancel stops running any further delegates. The same happens to the calling code, even when catching exceptions.
The last thing to note, is that the presence of the CancellationToken does not affect the logic flow of the methods. All lines in the method run, unless there is code explicitely exiting the method, for example, by throwing an exception. This is what happens if you pass the cancellation token to the Task.Delay calls and it gets cancelled from somewhere else before the time passes. It is also what happens if you were to put calls to CancellationToken.ThrowIfCancellationRequested after specific lines in your method.
It is not just the second task that fails to cancel. Both registrations to the token work and both ThrowIfCancellationRequested fire, but they are not handled because they run in a different thread.
This happens in the background (twice):
An exception of type 'System.OperationCanceledException' occurred in mscorlib.dll but was not handled in user code
What you should do is call cts.Token.ThrowIfCancellationRequested(); in your function instead of registering to the event.
See the examples at https://learn.microsoft.com/en-us/dotnet/standard/threading/cancellation-in-managed-threads
Right now you are combining two ways of cancellation: registering to the token cancel event (Token.Register), and throwing if the token is cancelled (Token.ThrowIfCancellationRequested).
Either you subscribe to the cancel event and perform your own cancel/cleanup logic, or you check in your function code if you should cancel your operation.
An example would look like this:
private static async Task CreateTask2(CancellationToken token)
{
try
{
// Pass on the token when calling other functions.
await Task.Delay(8000, token);
// And manually check during long operations.
for (int i = 0; i < 10000; i++)
{
// Do we need to cancel?
token.ThrowIfCancellationRequested();
// Simulating work.
Thread.SpinWait(5000);
}
Console.WriteLine("This is task two");
}
catch (Exception e)
{
Console.WriteLine("Task 2 was cancelled by Task 1");
Console.WriteLine(e);
}
}
Registration of a delegate by Register is just a way to notify when a token goes to the cancelled state, no more. In order to do the cancellation you need to react on this notification in the code and it's mostly needed when execution you want to cancel goes to a stage where cancellation token isn't verified (for example because a method being executed just doesn't accept CancellationToken as paramater) but you still need some control of cancellation state. But in all cases when you deal with executuon of code which has access to CancellationToken you just don't need to subscribe on the cancellation notification.
In your case the first delegate raises exception and this exception is propagated to the Cancel call only that's why the task is cancelled, but this is improper design as you shouldn't deal with CancellationTokenSource in your tasks and shouldn't initiate a cancellation in there, so I'd say that the first cancellation works only by coincidence. For the second task the delegate is invoked but nothing triggers the cancellation inside the task so why should it be cancelled ?

Task.WhenAll not throwing exception as expected

I have two async methods that I am running in the background of a form window as separate threads/tasks. These are infinite loops that just do some work in the background and then update the UI using the dispatcher. See below.
public async Task RunCameraThread(CancellationToken cancelToken)
{
while (true)
{
// If cancellation token is set, get out of the thread & throw a cancel exception
cancelToken.ThrowIfCancellationRequested();
// Get an image from the camera
CameraBitmap = Camera.CaptureImage(true);
// Update the UI (use lock to prevent simultaneous use of Dispatcher object in other thread)
lock (Dispatcher)
{
Dispatcher.Invoke(() => pictureBoxCamera.Image = tempBitmap);
Dispatcher.Invoke(() => pictureBoxCamera.Invalidate());
}
}
}
public async Task RunDistanceSensorThread(CancellationToken cancelToken)
{
while (true)
{
// If cancellation token is set, get out of the thread & throw a cancel exception
cancelToken.ThrowIfCancellationRequested();
// Get the distance value from the distance sensor
float distance = Arduino.AverageDistance(10, 100);
// Update the UI (use lock to prevent simultaneous use of Dispatcher object)
lock (Dispatcher)
{
Dispatcher.Invoke(() => textBoxDistanceSensor.Text = distance.ToString("0.00"));
}
}
}
These tasks are started on a button click (code shown below). I'm trying to use await Task.WhenAll in order to await both tasks. When the cancellation token is set this works as intended and an OperationCanceledException is caught. However, any exceptions thrown by issues with the Camera or Arduino (simulated by simply unplugging the USB during a run), does not seem to be caught.
private async void buttonConnect_Click(object sender, EventArgs e)
{
try
{
// Disable UI so we cannot click other buttons
DisableComponentsUI();
// Connect to Nimbus, Camera and Arduino
await Task.Run(() => Nimbus.ConnectAsync());
Camera.Connect();
Camera.ManagedCam.StartCapture();
Arduino.Connect();
// Get the current Nimbus positions and enable UI
UpdatePositionsUI();
EnableComponentsUI();
// Reset cancel token and start the background threads and await on them (this allows exceptions to bubble up to this try/catch statement)
StopTokenSource = new CancellationTokenSource();
var task1 = Task.Run(() => RunCameraThread(StopTokenSource.Token));
var task2 = Task.Run(() => RunDistanceSensorThread(StopTokenSource.Token));
await Task.WhenAll(task1, task2);
}
catch (OperationCanceledException exceptionMsg)
{
// Nothing needed here...
}
catch (Hamilton.Components.TransportLayer.ObjectInterfaceCommunication.ComLinkException exceptionMsg)
{
NimbusExceptionHandler(exceptionMsg);
}
catch (FlyCapture2Managed.FC2Exception exceptionMsg)
{
CameraExceptionHandler(exceptionMsg);
}
catch (IOException exceptionMsg)
{
ArduinoExceptionHandler(exceptionMsg);
}
catch (UnauthorizedAccessException exceptionMsg)
{
ArduinoExceptionHandler(exceptionMsg);
}
catch (TimeoutException exceptionMsg)
{
ArduinoExceptionHandler(exceptionMsg);
}
}
What's strange is that I see the exceptions thrown in the output window, but they don't bubble up to my try/catch. Also, if I simply await on one task it works as expected and the exception bubbles up.
Anyone have any idea what I'm doing wrong?
Thanks!
This line
await Task.WhenAll(task1, task2);
will throw AggregateException if it occurs in task1 and / or task2, and will contain exceptions from all the tasks inside.
BUT for this to occur (i.e. for you to receive AggregateException) all tasks should finish their execution.
So in your current state you will receive exception only when exceptions occurred in both tasks (sooner or later).
If you do need to stop all other tasks whenever one of them failed, you can try using for example Task.WhenAny instead of Task.WhenAll.
Another option would be to implement some manual synchronization - for example, introduce shared flag like "wasAnyExceptions", set it inside every task whenever exception in that task occur, and check it inside task loop to stop loop execution.
UPDATE based on comments
To clarify, Task.WhenAll(..) will return task. When this task is finished, it will contain AggregateException with exceptions from all failed tasks inside its Exception property.
If you await for such task it will throw unwrapped exception from the first faulted task in the list.
If you .Wait() for this task, you will receive AggregateException.

Can cancellation token be used at tasks method within?

I've just started working with tasks and I've come to some things I don't quite understand about calling methods within the task. I have started a new task like this:
var ts = new CancellationTokenSource();
var token = ts.Token;
Task.Run(() => Control(), token);
void Control()
{
while(!token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
switch(ENUM)
{
case SOMETHING:
StartSomething();
break;
}
Task.Delay(50, token).wait();
}
}
Now I don't understand the behavior of StartSomething() once token has been cancelled. What if StartSomething() as well contains a while loop, can I as well use?
!token.IsCancellationRequested
and
token.ThrowIfCancellationRequested();
As well, if the Cancellation exception is being thrown inside that StartSomething() loop, will it instantly cancel task?
Yes, you can easily pass the same token onto StartSomething and exceptions from it will bubble up to Control and cancel the task. If you don't then it will keep running even if the CancellationTokenwas cancelled until it returns control toControl` that observes the token:
void StartSomething(CancellationToken token)
{
while (true)
{
token.ThrowIfCancellationRequested(); // Will cancel the task.
// ...
}
}
Keep in mind though that token.ThrowIfCancellationRequested() will raise exception and the task will be canceled while !token.IsCancellationRequested will simply complete the task without marking it as canceled.

Stop my task and all my waiting task

i have an application who take all the added files from my Listbox and play this files:
IEnumerable<string> source
public void play()
{
Task.Factory.StartNew(() =>
{
Parallel.ForEach(source,
new ParallelOptions
{
MaxDegreeOfParallelism = 1 //limit number of parallel threads
},
file =>
{
//each file process via another class
});
}).ContinueWith(
t =>
{
OnFinishPlayEvent();
}
, TaskScheduler.FromCurrentSynchronizationContext() //to ContinueWith (update UI) from UI thread
);
}
my processing file can be stop via my class property but if i want to stop all the files
that waiting how can i do it ?
You need to design your routines to accept a CancellationToken, and then trigger a CancellationTokenSource.Cancel().
This will allow you to provide a mechanism to cooperatively cancel your work.
For details, see Cancellation in Managed Threads and Task Cancellation on MSDN.
If you want to stop a parallel loop, use an instance of the ParallelLoopState class. To cancel a task, you want to use a CancellationToken. Since you are embedding your parallel loop inside a task, you can simply pass a cancellation token into the task. Bear in mind, this will throw an OperationCanceledException you will have to catch, if you choose to wait on your task(s).
For example, and for the sake of argument, we'll assume that something else will call a delegate inside your class that will set the cancellation token.
CancellationTokenSource _tokenSource = new CancellationTokenSource();
//Let's assume this is set as the delegate to some other event
//that requests cancellation of your task
void Cancel(object sender, EventArgs e)
{
_tokenSource.Cancel();
}
void DoSomething()
{
var task = Task.Factory.StartNew(() => {
// Your code here...
}, _tokenSource.Token);
try {
task.Wait();
}
catch (OperationCanceledException) {
//Carry on, logging that the task was canceled, if you like
}
catch (AggregateException ax) {
//Your task will throw an AggregateException if an unhandled exception is thrown
//from the worker. You will want to use this block to do whatever exception handling
//you do.
}
}
Bear in mind, there are better ways to do this (and I'm typing from memory here, so there could be some syntax errors and the like), but this should get you started.

Categories

Resources