2013-03-03 79 views
41

我有以下的測試代碼:爲什麼發生TaskCanceledException?

void Button_Click(object sender, RoutedEventArgs e) 
{ 
    var source = new CancellationTokenSource(); 

    var tsk1 = new Task(() => Thread1(source.Token), source.Token); 
    var tsk2 = new Task(() => Thread2(source.Token), source.Token); 

    tsk1.Start(); 
    tsk2.Start(); 

    source.Cancel(); 

    try 
    { 
     Task.WaitAll(new[] {tsk1, tsk2}); 
    } 
    catch (Exception ex) 
    { 
     // here exception is caught 
    } 
} 

void Thread1(CancellationToken token) 
{ 
    Thread.Sleep(2000); 

    // If the following line is enabled, the result is the same. 
    // token.ThrowIfCancellationRequested(); 
} 

void Thread2(CancellationToken token) 
{ 
    Thread.Sleep(3000); 
} 

在線程方法我不拋出任何異常,但我在開始的任務外碼的try-catch塊得到TaskCanceledException。爲什麼發生這種情況,在這種情況下token.ThrowIfCancellationRequested();的目的是什麼。我相信只有在線程方法中調用token.ThrowIfCancellationRequested();時纔會拋出異常。

+0

這不會在VS2017 .NET Framework 4.6.2中引發異常。 – 2017-10-25 21:36:24

回答

19

我相信這是預期的行爲,因爲你正在跑步的競爭條件的變化。

How to: Cancel a task and its children

調用線程不強行結束任務;它僅表示要求取消。如果任務已經運行,則由用戶委託來注意請求並做出適當的響應。如果在任務運行之前請求取消,那麼用戶委託永遠不會執行,並且任務對象將轉換爲Canceled狀態。

Task Cancellation

您可以通過終止[...]簡單地從委託返回操作。在很多情況下,這是足夠的;但是,以這種方式「取消」的任務實例轉換爲RanToCompletion狀態,而不轉換爲Canceled狀態。

我這裏受教育的猜測是,當你在呼喚你的兩個任務.Start(),有機會,一個(或兩者)並沒有真正開始您在CancellationTokenSource.Cancel()之前。我敢打賭,如果你在任務開始和取消之間至少等待三秒鐘,那麼它不會拋出異常。此外,您可以檢查兩個任務的.Status屬性。如果我是對的,.Status屬性應該在拋出異常時至少讀取TaskStatus.Canceled

請記住,開始一個新的Task並不保證正在創建新的線程。它由TPL決定什麼獲得一個新線程,什麼是簡單排隊執行。

+0

是的,這是正確的。除了「由TPL決定什麼獲得新線程」之外,實際上問題中的Start()方法會立即將任務排隊在ThreadPool上,並且線程池(比TPL更低的線程池)決定何時實際執行工作。但是,如果在實際執行工作之前令牌被取消,任務仍將取消。 – 2013-03-03 17:09:13

+3

'Task.WaitAll'有些不好,因爲它在等待可能是異步工作的時候阻塞了一個線程。如果您調用Task.WhenAll而不是解鎖一個線程,但它不會拋出取消的任務。然而,如果您等待()或「等待」,那麼該方法將拋出的任務。 – 2013-03-03 17:10:04

+1

所以這是設計的,如果任務在開始之前被取消,它總會拋出異常。 – 2013-03-04 03:47:45