I was just introduced to Tasks (TPL) yesterday, and so I tried to do a little sample project in order to develop an understanding of how to use them.
My sample project is setup with a start button that begins incrementing a progressbar. A second button to cancel the task. A text box to report when the continuation using the TaskContinuationOptions.OnlyOnRanToCompletion is called, and a text box to report when the continuation using the TaskContinuationOptions.OnlyOnCanceled is called.
I can create and execute a Task, but canceling it in a way that lets a continuation with the TaskContinuationOptions.OnlyOnCanceled flag to fire, has been a problem.
I create the tasks as follows:
private void StartTask()
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task task = null;
task = Task.Factory.StartNew(() => DoWork(tokenSource), tokenSource.Token);
//A list<CancellationTokenSource> so that I can cancel the task when clicking a button on the UI Thread.
MyTasks.Add(tokenSource);
Task completed = task.ContinueWith(result => TaskCompleted(), TaskContinuationOptions.OnlyOnRanToCompletion);
Task canceled = task.ContinueWith(result => TaskCanceled(), TaskContinuationOptions.OnlyOnCanceled);
}
I cancel the task as follows:
private void CancelTasks()
{
foreach (CancellationTokenSource tokenSource in MyTasks)
{
tokenSource.Cancel();
}
}
My worker function is as follows:
private void DoWork(CancellationTokenSource tokenSource)
{
if (progressBar1.InvokeRequired)
{
progressBar1.Invoke(new Action(() => DoWork(tokenSource)));
return;
}
try
{
bool dowork = true;
while (dowork)
{
tokenSource.Token.ThrowIfCancellationRequested();
if (progressBar1.Value == progressBar1.Maximum)
{
dowork = false;
}
Thread.Sleep(1000);
progressBar1.PerformStep();
Application.DoEvents();
}
countCompleted++;
}
catch (OperationCanceledException)
{
}
}
In other posts that I have read, it has been suggested that tokenSource.Token.ThrowIfCancellationRequested() is what sets the condition evaluated by the TaskContinuationOptions.OnlyOnCanceled.
None of the examples that I have seen include the use of the:
catch (OperationCanceledException)
{
}
However, without it the programs stops when i call the tokenSource.Cancel();
As it stands, when I do call the tokenSource.Cancel(), the Continuation with the TaskContinuationOptions.OnlyOnRanToCompletion runs, instead of the TaskContinuationOptions.OnlyOnCanceled.
Clearly I'm not doing this correctly.
Edit:
Doing some further reading, I found a comment that states that:
"catch (OperationCanceledException) {} will set the task's status as RanToCompletion, not as Canceled"
So removing the catch (OperationCanceledException) {} allows the task's status to to be set to canceled, but the program breaks on the tokenSource.Token.ThrowIfCancellationRequested(); but if I then continue through the break, the continuation task with the TaskContinuationOptions.OnlyOnCanceled runs, which is good.
But how do I call tokenSource.Token.ThrowIfCancellationRequested() without allowing the program to break and while allowing the task status to be set to Canceled?
The comments above are correct in terms of the debugger and the options required to prevent the debugger breaking. However, the following should give you a better example of how to use continuations and indeed how to handle exceptions thrown from tasks within those continuations...
A continuation can find out if an exception was thrown by the antecedent Task
by the antecedent task's exception property. The following prints the results of a NullReferenceException
to the console
Task task1 = Task.Factory.StartNew (() => { throw null; });
Task task2 = task1.ContinueWith (ant => Console.Write(ant.Exception());
If task1
throws an exception and this exception is not captured/queried by the continuation it is considered unhandled and the application dies. With continuations it is enough to establish the result of the task via the Status
keyword
asyncTask.ContinueWith(task =>
{
// Check task status.
switch (task.Status)
{
// Handle any exceptions to prevent UnobservedTaskException.
case TaskStatus.RanToCompletion:
if (asyncTask.Result)
{
// Do stuff...
}
break;
case TaskStatus.Faulted:
if (task.Exception != null)
mainForm.progressRightLabelText = task.Exception.InnerException.Message;
else
mainForm.progressRightLabelText = "Operation failed!";
default:
break;
}
}
If you don't use continuations you either have to wait on the task in a try
/catch
block or query a task's Result
in a try
/catch
block
int x = 0;
Task<int> task = Task.Factory.StartNew (() => 7 / x);
try
{
task.Wait();
// OR.
int result = task.Result;
}
catch (AggregateException aggEx)
{
Console.WriteLine(aggEx.InnerException.Message);
}
Hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With