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