[英]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 上的所有其他操作完成時使用。
因此,在不同步的情況下同時從兩個不同的線程調用Cancel
和Dispose
不是一種選擇。 這僅留下一個選項:圍繞 CTS 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.