2017-04-05 86 views
3

在下面的代碼:Task.WhenAny - 任務被取消

 if (await Task.WhenAny(task, Task.Delay(100)) == task) { 
      success = true; 
     } 
     else { 
      details += "Timed out on SendGrid."; 
      await task.ContinueWith(s => 
      { 
       LogError(s.Exception); 
      }, TaskContinuationOptions.OnlyOnFaulted); 
     } 

我偶爾會調用await task.ContinueWith得到A task was cancelled。我的目標是看看task在100ms內完成 - 如果沒有,我想處理一些日誌記錄(這個特定的任務有一個資源泄漏,所以我試圖通過在超時中包裝它來解決它)。 Debugging Task.WhenAny and Push Notifications

爲什麼會這樣,我能做些什麼來防止被拋出此異常:這是正在從引導這裏拉?

回答

4

發生這種情況時,你的task無法按時完成(在100毫秒),但在以後完成。你用TaskContinuationOptions.OnlyOnFaulted運行你的延續,如果原始任務沒有錯誤,這些任務就會被取消。你await結果ContinueWith所以如果你的任務沒有錯誤 - 你的延續被取消,你有一個例外。

一般說來,這樣的方式來處理超時沒有多大意義,因爲達到超時即使你還是等到原來的任務完成,記錄異常。我認爲你必須在繼續之前刪除await。然後代碼會在超時時繼續執行,但如果任務稍後失敗 - 則會被記錄。

。在你的代碼中的另一個問題 - Task.WhenAny不會拋出。所以這種情況下:

await Task.WhenAny(task, Task.Delay(100)) == task 

並不意味着成功,因爲task可能會出現故障。即使WhenAny表示完成您的任務,始終檢查task.Statustask.Exception。順便說一下,你在你的問題中提到的答案提到了這一點。

更新:如果你不喜歡VS警告有關不期待已久的通話 - 你可以禁用這個特定的線路,或者使用擴展方法是這樣的:

static class TaskExtensions 
{ 
    public static void Forget(this Task task) 
    { 
     // do nothing 
    } 
} 

然後:

task.ContinueWith(s => { 
    Logger.Write(s.Exception); 
}, TaskContinuationOptions.OnlyOnFaulted).Forget(); 

這樣做並沒有什麼壞處(當然在這種情況下),VS只是在每個潛在的等待呼叫中發出這個警告,而這個警告並沒有等待。

+0

我並不完全清楚。鑑於我試圖處理漏泄的API,你會如何建議處理這個而不拋出任何異常(和只是日誌記錄)? – SB2055

+0

已更新答案 - 只是在task.ContinueWith之前刪除「await」。 – Evk

+0

然後VS抱怨我沒在等待異步調用。 – SB2055

0

如果任務繼續不運行,則取消任務。您的任務延續將運行OnlyOnFaulted。如果它沒有錯,那麼延續將被取消。

不涉及處理特定異常的兩個選擇是要麼不等待任何事情(並且不知道任務是否已完成運行),要麼等待原始任務。一旦等待完成,任務延續將在該點運行或取消。

如果你是罰款處理的取消,然後只需在任務延續趕上TaskCancelledException(或AggregateException如適用)根據需要和丟棄的除外。

在我看來,對於這種特殊情況(記錄異常),最好的辦法就是再次等待原始任務,並處理TaskCancelledException,不需要任務延續,完全異步/等待兼容。

0

正如@Evk提到的那樣,您的Continuation被取消。但我想補充一點,繼續可以被認爲是配置的一部分,並且因此在產生Task時被設置。考慮以下幾點:

var task = Task.Delay(500).ContinueWith(s => 
{ 
    LogError(s.Exception); 
}, TaskContinuationOptions.OnlyOnFaulted); 

if (await Task.WhenAny(task, Task.Delay(100)) == task) { 
    success = true; 
} else { 
    details += "Timed out on SendGrid."; 
} 

在這種方法您的例外仍然登錄,你的邏輯將繼續只知道該Task超時。如果除了超時之外,其餘的邏輯確實需要了解Exception,那麼您將需要再次參考已經討論的awaitTask

更新爲清楚起見

原始代碼(1)在這個階段,我們不能看到task定義。

//task is undeclared in this snippet 
if (await Task.WhenAny(task, Task.Delay(100)) == task) { 
    success = true; 
} 
else { 
    details += "Timed out on SendGrid."; 
    await task.ContinueWith(s => 
    { 
     LogError(s.Exception); 
    }, TaskContinuationOptions.OnlyOnFaulted); 
} 

(2)讓我們增加一個模擬的任務只是舉例

var task = Task.Delay(500); //defines task as a Task that will complete in 500ms 
if (await Task.WhenAny(task, Task.Delay(100)) == task) { 
    success = true; 
} 
else { 
    details += "Timed out on SendGrid."; 
    await task.ContinueWith(s => 
    { 
     LogError(s.Exception); 
    }, TaskContinuationOptions.OnlyOnFaulted); 
} 

(3)接下來,當我們await task.ContinueWith我們允許TaskCancelledException被拋出。如果刪除await,則可以忽略Exception,但我們會收到未等待任務的警告。我們可以忽略該警告,或者我們可以認識到continuation可以被視爲特定Task的配置的一部分。隨着我們可以配置我們用適當的選項創建任務的Continuation,即TaskContinuationOptions.OnlyOnFaulted

//Add continuation configuration where task is created 
var task = Task.Delay(500).ContinueWith(s => 
{ 
    LogError(s.Exception); 
}, TaskContinuationOptions.OnlyOnFaulted); 

if (await Task.WhenAny(task, Task.Delay(100)) == task) { 
    success = true; 
} else { 
    details += "Timed out on SendGrid."; 
    //removed continuation from here. 
} 
+0

我很困惑 - 這裏的原始「任務」在哪裏? – SB2055

+0

@ SB2055它仍然被命名爲'task',我剛剛爲它分配了一個'Task.Delay',目的是爲了表明你可以並且應該在任務配置時而不是超時之後設置「延續」。你可以用任何正在創建你的實際任務的'Task.Delay(500)'代替。 – JSteward

+0

我想這個答案會更好,如果它利用我提供的代碼。我並不瞭解你的意圖。 – SB2055