How to Correctly Cancel a TPL Task with Continuation
-
13. března 2012 14:43
I have a long running operation which I am putting on a background thread using TPL. What I have currently works but I am confused over where I should be handling my `AggregateException` during a cancellation request.
In a button click event I start my process:
private void button1_Click(object sender, EventArgs e)
{
Utils.ShowWaitCursor();
buttonCancel.Enabled = buttonCancel.Visible = true;
try
{
// Thread cancellation.
cancelSource = new CancellationTokenSource();
token = cancelSource.Token;// Get the database names.
string strDbA = textBox1.Text;
string strDbB = textBox2.Text;// Start duplication on seperate thread.
asyncDupSqlProcs =
new Task<bool>(state =>
UtilsDB.DuplicateSqlProcsFrom(token, mainForm.mainConnection, strDbA, strDbB),
"Duplicating SQL Proceedures");
asyncDupSqlProcs.Start();//TaskScheduler uiThread = TaskScheduler.FromCurrentSynchronizationContext();
asyncDupSqlProcs.ContinueWith(task =>
{
switch (task.Status)
{
// Handle any exceptions to prevent UnobservedTaskException.
case TaskStatus.Faulted:
Utils.ShowDefaultCursor();
break;
case TaskStatus.RanToCompletion:
if (asyncDupSqlProcs.Result)
{
Utils.ShowDefaultCursor();
Utils.InfoMsg(String.Format(
"SQL stored procedures and functions successfully copied from '{0}' to '{1}'.",
strDbA, strDbB));
}
break;
case TaskStatus.Canceled:
Utils.ShowDefaultCursor();
Utils.InfoMsg("Copy cancelled at users request.");
break;
default:
Utils.ShowDefaultCursor();
break;
}
}, TaskScheduler.FromCurrentSynchronizationContext()); // Or uiThread.return;
}
catch (Exception)
{
// Do stuff...
}
}In the method `DuplicateSqlProcsFrom(CancellationToken _token, SqlConnection masterConn, string _strDatabaseA, string _strDatabaseB, bool _bCopyStoredProcs = true, bool _bCopyFuncs = true)` I have
DuplicateSqlProcsFrom(CancellationToken _token, SqlConnection masterConn, string _strDatabaseA, string _strDatabaseB, bool _bCopyStoredProcs = true, bool _bCopyFuncs = true)
{
try
{
for (int i = 0; i < someSmallInt; i++)
{
for (int j = 0; j < someBigInt; j++)
{
// Some cool stuff...
}if (_token.IsCancellationRequested)
_token.ThrowIfCancellationRequested();
}
}
catch (AggregateException aggEx)
{
if (aggEx.InnerException is OperationCanceledException)
Utils.InfoMsg("Copy operation cancelled at users request.");
return false;
}
catch (OperationCanceledException)
{
Utils.InfoMsg("Copy operation cancelled at users request.");
return false;
}
}In a button Click event (or using a `delegate` (buttonCancel.Click += delegate { /*Cancel the Task*/ }`) I cancel the `Task` as follows:
private void buttonCancel_Click(object sender, EventArgs e)
{
try
{
cancelSource.Cancel();
asyncDupSqlProcs.Wait();
}
catch (AggregateException aggEx)
{
if (aggEx.InnerException is OperationCanceledException)
Utils.InfoMsg("Copy cancelled at users request.");
}
}This catches the `OperationCanceledException` fine in method `DuplicateSqlProcsFrom` and prints my message, but in the call-back provided by the `asyncDupSqlProcs.ContinueWith(task => { ... });` above the `task.Status` is always `RanToCompletion`; it should be cancelled!
What is the right way to capture and deal with the `Cancel()` task in this case. I know how this is done in the simple cases shown in [this example from the CodeProject](http://www.codeproject.com/Articles/152765/Task-Parallel-Library-1-of-n#handlingExceptions) and from the [examples on MSDN](http://msdn.microsoft.com/en-us/library/dd537607.aspx) but I am confused in this case when running a continuation.
How do I capture the cancel task in this case and how to ensure the `task.Status` is dealt with properly?
"Everything should be made as simple as possible, but not simpler" - Einstein
Všechny reakce
-
13. března 2012 15:36
If you want the exception to propagate to the caller, you should replace
return false;
in the catch clause with
throw;
Using the keyword 'throw' without any parameter rethrows the exception you just caught.
-
13. března 2012 15:43
I have managed to work that out but I still get `task.Status` as `TaskStatus.Faulted` event when I cancel. The inner exception caught in the countinuation is a OperationCancelledException, but I still get the Faulted condition. Any Ideas on that one?
Thanks very much for your time.
"Everything should be made as simple as possible, but not simpler" - Einstein
-
13. března 2012 16:19Actually you cannot throw from this execption. It gives an unhandled exception error!?
"Everything should be made as simple as possible, but not simpler" - Einstein
-
14. března 2012 15:00
To get an OperationCancelledException, it needs to be thrown with the same token as the one passed to the Task's constructor.
new Task<bool>(state => UtilsDB.DuplicateSqlProcsFrom(token, mainForm.mainConnection, strDbA, strDbB), "Duplicating SQL Proceedures", token);
- Označen jako odpověď Camuvingian 14. března 2012 17:50