2015-04-14 75 views
14

我使用取消令牌傳遞,以便我的服務可以乾淨地關閉。該服務具有不斷嘗試連接到其他服務的邏輯,因此該令牌是突破在單獨線程中運行的這些重試循環的好方法。我的問題是我需要調用具有內部重試邏輯的服務,但如果重試失敗,則要在一段時間後返回。我想創建一個新的取消令牌,並且會爲我執行此操作。問題在於我的新令牌沒有鏈接到「主」令牌,所以當主令牌被取消時,我的新令牌仍然處於活動狀態,直到它超時或連接完成並返回。我想要做的就是將兩個令牌連接在一起,這樣當主人被取消時,我的新人也將被取消。我嘗試使用CancellationTokenSource.CreateLinkedTokenSource方法,但是當我的新令牌超時時,它也取消了主令牌。有沒有辦法做什麼,我需要用代幣做什麼還是會需要改變重試邏輯(大概不會是能夠做到這一點很容易)鏈接取消令牌

這是我想做的事:

Master Token - 傳遞各種功能,以便服務可以乾淨地關閉。 臨時令牌 - 傳遞給單個功能並在一分鐘後設置爲超時

如果取消主令牌,臨時令牌也必須取消。

當臨時令牌到期時,它不能取消主令牌。

回答

17

您想使用CancellationTokenSource.CreateLinkedTokenSource。它允許有一個「父母」和一個「孩子」。這裏有一個簡單的例子:

var parentCts = new CancellationTokenSource(); 
var childCts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token); 

childCts.CancelAfter(1000); 
Console.WriteLine("Cancel child CTS"); 
Thread.Sleep(2000); 
Console.WriteLine("Child CTS: {0}", childCts.IsCancellationRequested); 
Console.WriteLine("Parent CTS: {0}", parentCts.IsCancellationRequested); 
Console.WriteLine(); 

parentCts.Cancel(); 
Console.WriteLine("Cancel parent CTS"); 
Console.WriteLine("Child CTS: {0}", childCts.IsCancellationRequested); 
Console.WriteLine("Parent CTS: {0}", parentCts.IsCancellationRequested); 

產量預期:

取消孩子CTS
兒童CTS:真
家長CTS:假

取消父CTS
兒童CTS:真
Parent CTS:True

+2

你是非常正確的。我開始使用CancellationTokenSource.CreateLinkedTokenSource,但認爲它不起作用。我忘記了當令牌超時時會拋出異常。這在我的代碼中被進一步捕獲。這給人的印象是沒有按我的預期工作。通過將我的調用放在try catch塊中,它工作正常。 – Retrocoder

+0

@Retrocoder如果你只想捕獲內部的令牌,我會推薦使用像'try {doSomething(ct:childCts.Token); } catch(OperationCancelledException)when(childCts.IsCancellationRequested){}'。你可以把它放在重試循環中,並在循環內創建子令牌源。然後,當父令牌取消時,它會一直向上冒泡,但是當子令牌取消時,它只會重試。我無法從你的評論中得知 - 你可能已經正確地做到了這一點;-)。 – binki

0

作爲i3arnon already answered,你可以用CancellationTokenSource.CreateLinkedTokenSource()做到這一點。我想試圖展示一種如何使用這種標記的模式,以便在不取消整體任務的情況下區分取消總體任務與取消兒童任務。

async Task MyAsyncTask(
    CancellationToken ct) 
{ 
    // Keep retrying until the master process is cancelled. 
    while (true) 
    { 
     // Ensure we cancel ourselves if the parent is cancelled. 
     ct.ThrowIfCancellationRequested(); 

     var childCts = CancellationTokenSource.CreateLinkedTokenSource(ct); 
     // Set a timeout because sometimes stuff gets stuck. 
     childCts.CancelAfter(TimeSpan.FromSeconds(32)); 
     try 
     { 
      await DoSomethingAsync(childCts.Token); 
     } 
     // If our attempt timed out, catch so that our retry loop continues. 
     // Note: because the token is linked, the parent token may have been 
     // cancelled. We check this at the beginning of the while loop. 
     catch (OperationCancelledException) when (childCts.IsCancellationRequested) 
     { 
     } 
    } 
} 

當臨時令牌到期必須不會取消主令牌。

請注意,MyAsyncTask()的簽名接受CancellationToken而不是CancellationTokenSource。由於該方法只能訪問CancellationToken上的成員,因此它不會意外取消主/父令牌。我建議您以這樣的方式組織代碼,使主任務的CancellationTokenSource儘可能少地顯示代碼。在大多數情況下,這可以通過將CancellationTokenSource.Token傳遞給方法來完成,而不是共享對CancellationTokenSource的引用。

我還沒有調查,但可能有反射的方式來強制取消CancellationToken而無需訪問其CancellationTokenSource。希望這是不可能的,但如果可能的話,這將被認爲是不好的做法,並不是普遍擔心的事情。