もくじ
https://tera1707.com/entry/2022/02/06/144447
やりたいこと
以前の自分の記事で、Taskのキャンセルの仕方を調べた。
そこで、なんとなくキャンセルの仕方がわかったのだが、下記の記事を拝見して、さらに便利にキャンセルする方法があると知った。
そこで知ったことを自分なりにまとめたい。
やったこと
複数の理由でキャンセルして、OperationCanceledExceptionに入っているtokenを見てどの理由かを知る
CancellationTokenSource.CreateLinkedTokenSource()
で複数のTokenをつなげる。- つなげたTokenで、キャンセルを検知し、
OperationCanceledException
をthrowする。 - throwされた
OperationCanceledException
をcatchしたときに、つなげたtokenのどれのIsCancellationRequested
が立っているかを見る。
という手順で見れる。
using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using System.Windows; namespace CancellationTokenJikken { public partial class MainWindow : Window { CancellationTokenSource cts; CancellationTokenSource cts2; public MainWindow() => InitializeComponent(); private async Task MugenLoop(CancellationToken token1, CancellationToken token2) { using var linked = CancellationTokenSource.CreateLinkedTokenSource(token1, token2); try { await Task.Delay(10000, linked.Token); } catch (OperationCanceledException tce) when (tce.CancellationToken == linked.Token) { if (tce.CancellationToken.IsCancellationRequested) { if (token1.IsCancellationRequested) Debug.WriteLine("トークン1でキャンセル"); if (token2.IsCancellationRequested) Debug.WriteLine("トークン2でキャンセル"); } } } private async void Button_Click(object sender, RoutedEventArgs e) { cts = new CancellationTokenSource(); cts2 = new CancellationTokenSource(); await MugenLoop(cts.Token, cts2.Token); } // キャンセル1 private void Button_Click_2(object sender, RoutedEventArgs e) { cts.Cancel(); } // キャンセル2 private void Button_Click_3(object sender, RoutedEventArgs e) { cts2.Cancel(); } } }
「.NETのクラスライブラリ設計 」のP303のページ下部に書かれてる「訳注」が、これのことかな?と思う。
タイムアウトをCancellationTokenSourceで実装する
CancellationTokenSource
にはCancelAfter(TimeSpan)
というメソッドがあり、それでタイムアウトを設定できる。
タイムアウトというのはつまり、tokenを指定の秒数で自動でCancelするようにできるということ。
使い方は下記のようにする。
using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using System.Windows; namespace CancellationTokenJikken { public partial class MainWindow : Window { CancellationTokenSource cts; public MainWindow() => InitializeComponent(); private async Task MugenLoop(CancellationToken cancellationToken) { using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); cts.CancelAfter(TimeSpan.FromSeconds(5));// 5秒でキャンセル await Task.Run(() => { try { while (true) { // この無限に続くループをキャンセルorタイムアウトさせる Thread.Sleep(1000); cts.Token.ThrowIfCancellationRequested(); } } catch (OperationCanceledException tce) when (tce.CancellationToken == cts.Token) { if (cancellationToken.IsCancellationRequested) { Debug.WriteLine("外のtokenでキャンセル"); } else { Debug.WriteLine("タイムアウトでキャンセル"); } } }).ConfigureAwait(false); } private async void Button_Click(object sender, RoutedEventArgs e) { cts = new CancellationTokenSource(); await MugenLoop(cts.Token); } // キャンセル1 private void Button_Click_2(object sender, RoutedEventArgs e) { cts.Cancel(); } } }
キャンセルされたことをCallbackで知る
下記のようにすると、CancellationTokenSourceがキャンセルされたときに呼ばれるActionを設定できる。 正直、どういうときに役立つかは呑み込めてない。
※参考にさせて頂いたこちらでは、こちらのtokenがキャンセルされたら、別のtokenもキャンセルする、みたいな使い方をされている様子だが、詳細は難しくて見切れなかった...
cts = new CancellationTokenSource(); cts.Token.Register(() => { Debug.WriteLine("キャンセルされました"); });
参考
async/awaitのキャンセル処理やタイムアウトを効率的に扱うためのパターン&プラクティス
https://neue.cc/2022/07/13_Cancellation.html
.NETのクラスライブラリ設計
この本の「9.2 非同期パターン」がめちゃ参考になった。
リンク