[英]How to Correctly Cancel a TPL Task with Continuation
我有一个长时间运行的操作,我正在使用TPL的后台线程。 我目前的工作原理,但我很困惑在取消请求期间我应该在哪里处理我的AggregateException
。
在按钮单击事件中,我启动了我的进程:
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...
}
}
在方法DuplicateSqlProcsFrom(CancellationToken _token, SqlConnection masterConn, string _strDatabaseA, string _strDatabaseB, bool _bCopyStoredProcs = true, bool _bCopyFuncs = true)
我有
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;
}
}
在按钮Click事件中(或使用delegate
(buttonCancel.Click + = delegate {/ Cancel the Task /} ) I cancel the
Task`,如下所示:
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.");
}
}
这会在方法DuplicateSqlProcsFrom
捕获OperationCanceledException
并打印我的消息,但是在asyncDupSqlProcs.ContinueWith(task => { ... });
提供的asyncDupSqlProcs.ContinueWith(task => { ... });
在task.Status
RanToCompletion
。状态总是RanToCompletion
; 它应该被取消!
在这种情况下,捕获和处理Cancel()
任务的正确方法是什么。 我知道这个例子是在CodeProject和MSDN上的示例中显示的简单情况下完成的,但在运行延续时我很困惑。
在这种情况下如何捕获取消任务以及如何确保task.Status
正确处理?
您正在捕获DuplicateSqlProcsFrom
方法中的OperationCanceledException
,这会阻止其Task
看到它并相应地将其状态设置为Canceled
。 由于处理了异常, DuplicateSqlProcsFrom
完成而不抛出任何异常,并且其相应的任务在RanToCompletion
状态下完成。
DuplicateSqlProcsFrom
不应该捕获OperationCanceledException
或AggregateException
,除非它正在等待它自己的子任务。 抛出的任何异常(包括OperationCanceledException
)都应该保持不被传播到延续任务。 在你延续的switch
语句,你应该检查task.Exception
在Faulted
情况和处理Canceled
在适当的情况下也是如此。
在你的continuation lambda中, task.Exception
将是一个AggregateException
,它有一些方便的方法来确定错误的根本原因,并处理它。 检查MSDN文档特别是InnerExceptions
(注意“S”), GetBaseException
, Flatten
和Handle
成员。
编辑 :获取一个Faulted
的TaskStatus
而不是Canceled
。
在构造asyncDupSqlProcs
任务的行上,使用Task
构造函数,该构造函数接受DuplicateSqlProcsFrom
委托和CancellationToken
。 这会将您的令牌与任务相关联。
在DuplicateSqlProcsFrom
对令牌调用ThrowIfCancellationRequested
时,抛出的OperationCanceledException
包含对已取消的令牌的引用。 当Task捕获异常时,它会将该引用与与之关联的CancellationToken
进行比较。 如果匹配,则任务转换为Canceled
。 如果他们不这样做,则编写任务基础结构以假设这是一个无法预料的错误,并且任务转换为Faulted
。
Sacha Barber有很多关于TPL的文章。 试试这一个 ,他描述了简单的任务与延续和取消
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.