簡體   English   中英

處理取消令牌源的正確模式

[英]Correct pattern to dispose of cancellation token source

考慮一個場景,您有一些異步工作要完成,您可以在“一勞永逸”模式下運行它。 此異步工作能夠偵聽取消,因此您將取消令牌傳遞給它以便能夠取消它。

在給定的時間,我們可以決定請求取消正在進行的活動,方法是使用取消令牌源 object,我們從中獲取了取消令牌。

因為取消令牌源實現了IDisposable ,所以我們應該調用它的Dispose方法,只要我們完成它。 這個問題的重點是准確確定您何時使用給定的取消令牌源。

假設您決定通過在取消令牌源上調用Cancel方法來取消正在進行的工作:在調用Dispose之前是否需要等待正在進行的操作完成

換句話說,我應該這樣做:

class Program 
{
  static void Main(string[] args) 
  {
    var cts = new CancellationTokenSource();
    var token = cts.Token;

    DoSomeAsyncWork(token); // starts the asynchronous work in a fire and forget manner

    // do some other stuff here 

    cts.Cancel();
    cts.Dispose(); // I call Dispose immediately after cancelling without waiting for the completion of ongoing work listening to the cancellation requests via the token

    // do some other stuff here not involving the cancellation token source because it's disposed
  }

  async static Task DoSomeAsyncWork(CancellationToken token) 
  {
     await Task.Delay(5000, token).ConfigureAwait(false);
  }
}

或者這樣:

class Program 
{
  static async Task Main(string[] args) 
  {
    var cts = new CancellationTokenSource();
    var token = cts.Token;

    var task = DoSomeAsyncWork(token); // starts the asynchronous work in a fire and forget manner

    // do some other stuff here 

    cts.Cancel();

    try 
    {
      await task.ConfigureAwait(false);
    }
    catch(OperationCanceledException) 
    {
      // this exception is raised by design by the cancellation
    }
    catch (Exception) 
    {
      // an error has occurred in the asynchronous work before cancellation was requested
    }

    cts.Dispose(); // I call Dispose only when I'm sure that the ongoing work has completed

    // do some other stuff here not involving the cancellation token source because it's disposed
  }

  async static Task DoSomeAsyncWork(CancellationToken token) 
  {
     await Task.Delay(5000, token).ConfigureAwait(false);
  }
}

附加細節:我指的代碼寫在 ASP.NET 核心 2.2 web 應用程序中,這里我使用控制台應用程序場景只是為了簡化我的示例。

我在 stackoverflow 上發現了類似的問題,要求處理取消令牌源對象。 一些答案表明,在某些情況下處理這個 object 並不是真正需要的。

我對整個IDisposable主題的處理方法是,我總是傾向於遵守 class 的公開合同,換一種說法,如果 object 聲稱是一次性的,我更願意在完成后始終調用Dispose 我不喜歡根據 class 的實現細節來猜測是否真的需要調用 dispose 的想法,這些細節可能會在未來的版本中以未記錄的方式發生變化。

為確保最終處置與即發即棄的Task關聯的 CTS ( CancellationTokenSource ),您應該將延續附加到任務,並從延續內部處置 CTS。 但是,這會產生一個問題,因為另一個線程可能會在 object 處於處理過程中時調用Cancel方法,並且根據文檔Dispose方法不是線程安全的:

CancellationTokenSource的所有公共和受保護成員都是線程安全的,並且可以從多個線程同時使用,但Dispose()除外,它必須僅在CancellationTokenSource object 上的所有其他操作完成時使用。

因此,在不同步的情況下同時從兩個不同的線程調用CancelDispose不是一種選擇。 這僅留下一個選項:圍繞 CTS class 的所有公共成員添加一層同步。 不過,這不是一個令人愉快的選擇,原因如下:

  1. 您必須編寫線程安全的包裝器 class(編寫代碼)
  2. 每次啟動可取消的即發即棄任務時都必須使用它(編寫更多代碼)
  3. 導致同步的性能損失
  4. 招致附加延續的性能損失
  5. 必須維護一個變得更復雜、更容易出錯的系統
  6. 必須解決哲學問題,為什么 class 首先不是設計為線程安全的

因此,我的建議是替代方案,即僅在無法等待其相關任務完成的情況下不處理 CTS。 換句話說,如果無法將使用 CTS 的代碼包含在using語句中,則只需讓垃圾收集器來回收保留的資源即可。 這意味着您必須不遵守文檔的這一部分:

在發布對CancellationTokenSource的最后引用之前,請始終調用 Dispose。 否則,在垃圾收集器調用CancellationTokenSource對象的Finalize方法之前,它正在使用的資源不會被釋放。

...還有這個

CancellationTokenSource class 實現了IDisposable接口。 當您完成使用取消令牌源以釋放它持有的任何非托管資源時,您應該確保調用CancellationTokenSource.Dispose方法。

如果這讓你覺得有點臟,你並不孤單。 如果您認為Task class 也實現了IDisposable接口,您可能會感覺更好,但不需要處理任務實例。

正確的做法是第二個- 在您確定任務被取消后處置CancellationTokenSource CancellationToken正確依賴從CancellationTokenSource到 function 的信息。 雖然當前實現CancellationToken的編寫方式即使在不拋出異常的情況下仍然可以工作,如果創建它的 CTS 被釋放,它可能無法正常運行或始終按預期運行。

像任何IDisposable一樣,您在使用完資源后將其丟棄。 這是IDisposable的硬性規則,我還沒有遇到過不是這種情況的情況,但我當然願意學習;)。

CancellationTokenSource的情況下,這意味着您在 object 本身和Token屬性都不再使用時處置源。 (我剛剛為這個聲明打開了一個來源,但可惜我分心了,不知何故丟失了它)

因此,您在任務不再使用CancellationToken時進行處置。 在您的情況下,第二個選項,因為您確定沒有任務使用令牌。

編輯; 除此之外,它的良好做法是還為實現一次性的 null 設置任何屬性。 在這種情況下,因為您只有局部變量,這並不重要,但是當您將令牌源作為字段或其他內容時,請確保將該字段設置為 null 以便沒有對令牌源的引用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM