CancellationToken 非同步取消工作

在 .Net Core 世界中,非同步執行變成最基本的內容,甚至現在大部分系統設計理念皆是如此,若你對多執行序稍有了解,應該能理解放出去的 task 是很難掌控跑去哪的。

這次要介紹的是 CancellationTokenCancellationTokenSource

CancellationToken

首先來看這個物件的 Property

  • CanBeCanceled: 此 token 是否可以被取消執行
  • CanllationRequested: 此 token 是否被請求取消
  • None: 直接使用空的 token,無法被取消或者請求取消
  • WaitHandle: 阻塞當前執行緒直到收到取消請求,再繼續執行

在使用各種建構方式,其屬性分別是甚麼內容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var token = new CancellationToken();
Console.WriteLine($"CanBeCanceld:{token.CanBeCanceled}, IsCancellationRequested:{token.IsCancellationRequested}");

var tokenTrue = new CancellationToken(true);
Console.WriteLine($"CanBeCanceld:{tokenTrue.CanBeCanceled}, IsCancellationRequested:{tokenTrue.IsCancellationRequested}");

var tokenNone = CancellationToken.None;
Console.WriteLine($"CanBeCanceld:{tokenNone.CanBeCanceled}, IsCancellationRequested:{tokenNone.IsCancellationRequested}");

---
myctw.cc
CanBeCanceld:False, IsCancellationRequested:False
CanBeCanceld:True, IsCancellationRequested:True
CanBeCanceld:False, IsCancellationRequested:False

先前常見的無限執行方法:

1
2
3
4
5
6
7
8
9
10
public static void InfiniteLoop()
{
while(true)
{
if ( !cond ) break;

Console.WriteLine("Loop myctw.cc ..");
Thread.Sleep(1000);
}
}

現在也建議採用正規作法,透過 CancellationToken 來傳遞是否取消執行的訊息

1
2
3
4
5
6
7
8
public static void InfiniteLoop(CancellationToken token)
{
while(!token.IsCancellationRequested)
{
Console.WriteLine("Loop myctw.cc ..");
Thread.Sleep(1000);
}
}

透過這個物件的屬性我們能夠操控當前任務(task)是否需要取消,不過我們一般會與他的實例化的物件 CancellationTokenSource 一併使用,而 CancellationToken 僅負責當作參數傳遞。

CancellationTokenSource

用來實例化(instantiate) CancellationToken 的物件,簡單來說就是 Token 負責夾帶訊息,而 Source 負責指揮 Token 如何處理。

建構子

  • CancellationTokenSource(int32): 過了 ms 後,自動執行取消
  • CancellationTokenSource(timespan): 過了設定的 timespan 後,自動執行取消

屬性

  • IsCancellationRequested: 如同 toekn 的屬性,取消是否被請求
  • Token: 即 CancellationToken

方法

方法很多,舉幾個常使用的

  • Cancel(): 執行取消請求
  • CancelAfter(int32): 於 ms 後,執行取消請求
  • Dispose: CancellationTokenSource 執行取消後,用於解構的方法

範例

上面介紹了無限迴圈的取消工作方法,這邊改用 Token 自帶的 ThrowIfCancellationRequested 方法取消

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static async Task DemoCancelMethod()
{
var src = new CancellationTokenSource();

for(int i = 0;i < 5;++i)
{
Task.Run(() => DoHardWork(src.Token), src.Token);
}
Thread.Sleep(1100);
Console.WriteLine("Call cancel");
src.Cancel();
Console.WriteLine("Call cancel done");
}

public static async Task DoHardWork(CancellationToken ct)
{
while(true)
{
Console.WriteLine("Do work hard");
Thread.Sleep(1000);
ct.ThrowIfCancellationRequested();
}
}

總結一下,其實這個概念很簡單。

  • 非同步的方法都要帶入 token,使得工作得以取消,否則在啟動關閉服務時都會遇到噴錯的情形
  • 也要知道呼叫 Cancel 的觸發點放在哪個位置比較好。
  • 如果還需要處理 cancel 的 exception,可以在 catch 中使用 OperationCanceledException 來捕捉,並執行你要的處置。
  • While (true) 不是不行,只是程式很容易寫出邏輯 bug,導致永遠出不來。

Reference:

  • 作者: MingYi Chou
  • 版權聲明: 轉載不用問,但請註明出處!本網誌均採用 BY-NC-SA 許可協議。