1

我有一个长时间运行的操作,我正在使用TPL在后台线程上。我目前的工作,但我很困惑,我应该在取消请求期间处理我的AggregateException如何正确取消TPL任务(续)

在按钮单击事件,我开始我的过程:

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 + =代理{/ 取消任务 /} ) I cancel the任务如下:

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."); 
    } 
} 

这样捕获OperationCanceledException罚款方法DuplicateSqlProcsFrom并打印我的消息,但在asyncDupSqlProcs.ContinueWith(task => { ... });提供的回拨提供task.Status以上总是RanToCompletion;它应该被取消!

在这种情况下捕获和处理Cancel()任务的正确方法是什么?我知道如何在this example from the CodeProjectexamples on MSDN中显示的简单情况下完成这项工作,但在运行延续时我很困惑。

在这种情况下如何捕获取消任务以及如何确保task.Status得到正确处理?

回答

3

你在你的DuplicateSqlProcsFrom方法,防止其从Task曾经看到它和它的地位相应设置为Canceled捕捉OperationCanceledException。因为处理了异常,所以DuplicateSqlProcsFrom完成而没有抛出任何异常,其相应的任务在RanToCompletion状态下结束。

DuplicateSqlProcsFrom不应该捕获OperationCanceledExceptionAggregateException,除非它在等待其自己的子任务。抛出的任何异常(包括OperationCanceledException)都应该保持未被捕获以传播到继续任务。在你的继续的switch声明中,您应该在相应的情况下检查task.Exception中的Faulted个案并处理Canceled

在你的延续lambda中,task.Exception将是一个AggregateException,它有一些方便的方法来确定错误的根本原因是什么,并处理它。请检查MSDN docs,尤其是InnerExceptions(注意“S”),GetBaseException,FlattenHandle成员。


编辑:在得到FaultedTaskStatus而不是Canceled

在构建asyncDupSqlProcs任务的线上,使用构造函数Task,该构造函数接受DuplicateSqlProcsFrom委托和CancellationToken。这将您的令牌与任务关联起来。

当您在DuplicateSqlProcsFrom的令牌上调用ThrowIfCancellationRequested时,抛出的OperationCanceledException包含对被取消的令牌的引用。当任务捕捉到异常时,它会将该引用与与其关联的CancellationToken进行比较。如果它们匹配,则任务转换为Canceled。如果他们不这样做,则任务基础结构已经写入以假定这是一个无法预料的错误,并且该任务转而改为Faulted

Task Cancellation in MSDN

+0

谢谢你。它真的有帮助。我已经到了从'DuplicateSqlProcsFrom'方法中删除handeling的阶段,但仍然试图在其他地方捕获它。 – MoonKnight 2012-03-13 13:59:25

+0

我按照你的建议完成了,但在我的延续方法中,我只能得到'TaskStatus.Faulted'条件,从来没有'TaskStatus.Cancelled'。你知道这可能是为什么吗? – MoonKnight 2012-03-13 14:12:49

+0

我已经为此答案添加了解释和解决方案。 – shambulator 2012-03-13 15:18:37

0

萨沙巴伯有关于第三方物流的一系列文章。试试这个one,他描述了简单的任务与延续和取消

+1

虽然这可能理论上回答这个问题,[这将是优选的](http://meta.stackexchange.com/q/8259),包括在这里的答案的主要部分,并提供链接供参考。 – 2012-03-13 12:04:19

+0

这是我提供的链接。尽管他涵盖了任务的取消,但它并不在我所处的更复杂的场景中 - 因此对我来说没有用处。谢谢你的时间。 – MoonKnight 2012-03-13 12:14:37