C# で非同期処理(async/await)を使用する際、気を付けなければいけないことの1つに「キャンセル処理」があります。
非同期処理では適切なキャンセル処理を実装しないと、不要な処理が実行されて CPU やメモリを消費し続ける可能性があります。また、デッドロックやデータ不整合を引き起こす可能性があります。
そのため、非同期処理においてキャンセルはリソース管理やパフォーマンスの観点から重要です。

この記事ではCancellationTokenを使用して非同期のタスクをキャンセルする方法をサンプルコードを交えながら紹介します。ぜひ参考にしてみてください。

オススメの参考書
C#の使い方が丁寧に解説しており、「基礎からしっかりと学びたい」という初心者の方にオススメの一冊です。サンプルコードも記載してあり、各章の最後に復習問題があるので理解度を確認しながら読み進めることができます。新しい C# のバージョンにも対応している書籍です。
CancellationTokenとは?

CancellationTokenは、非同期処理を実装する時に非同期処理をキャンセルするための仕組みです。
CancellationTokenSource クラスのオブジェクトを生成し、CancellationTokenをタスクに渡すことで、タスクの実行中にキャンセルをトリガーにすることができます。
非同期メソッド側でCancellationTokenを受け取れるように、メソッドの引数の最後でCancellationTokenを受け取るようにします。
// メソッドの引数の最後でCancellationTokenを受け取る
async Task TaskAsync(string str, CancellationToken cancellationToken)
{
// 何かしらの処理
}非同期メソッドの内部で更に別の非同期メソッドを呼び出す場合は、受け取ったCancellationTokenを別の非同期メソッドに渡します。このようにしてCancellationTokenを伝搬させていくことにより、非同期処理のキャンセルを実現します。
非同期処理をキャンセルする方法

非同期のキャンセル処理ですが、以下の内容を実装する必要があります。
CancellationTokenSourceのオブジェクトを作成する- 非同期メソッドの引数に
CancellationTokenをつける - 非同期メソッドに
CancellationTokenを渡す - キャンセルしたいタイミングで
Cancelメソッドを呼び出す try–catchで例外(OperationCancelledException)をキャッチする
上から順番に説明していきます。
CancellationTokenSourceのオブジェクトを作成する
キャンセルの制御を行うために、CancellationTokenSourceクラスのオブジェクトを作成します。
var cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;非同期メソッドの引数にCancellationTokenをつける
非同期のメソッドの引数の最後にCancellationTokenを実装します。CancellationTokenSourceオブジェクトのCancellationTokenを引数として、このメソッドに渡します。
async Task TaskAsync(int count, CancellationToken cancellationToken)
{
// 何かしらの処理
}非同期メソッドの内部は引数のcountの数だけループして数値を加算する処理にします。
ここで重要なのが、1つのループ毎にThrowIfCancellationRequestedメソッドを呼びして、キャンセル要求があった場合に例外をスローして処理を中断できるようにしておくことです。
またはif条件でIsCancellationRequestedプロパティの状態をチェックして、trueなら処理を中断できるようにします。
async Task TaskAsync(int count, CancellationToken cancellationToken)
{
var sum = 0;
await Task.Run(() =>
{
for (int i = 0; i < count; i++)
{
// キャンセルが要求されたら例外をスローするメソッド
cancellationToken.ThrowIfCancellationRequested();
// if (cancellationToken.IsCancellationRequested) { return; }
sum += 1;
Debug.WriteLine($"加算結果:{sum}");
}
}, cancellationToken);
}Cancelメソッドを呼び出す
キャンセルするには、CancellationTokenSourceのCancelメソッドを呼び出します。
var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();例外をキャッチする
非同期処理をキャンセルするとOperationCanceledExceptionという例外が発行されるので、try–catchで例外をキャッチします。
この例外が非同期処理メソッド内から外に向かって発行された場合、そのメソッドに紐づいたタスクはキャンセル扱いになります。
var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();サンプルコード
上記の内容を踏まえて、以下のサンプルコードを確認してみましょう。
スタートボタンをクリックすると、非同期で10000回ループして1を加算するコードです。加算している最中にキャンセルボタンをクリックすると、加算を中断します。
private CancellationTokenSource cancellationTokenSource;
// スタートボタンのクリックイベントハンドラ
async void Button_Click(object sender, RoutedEventArgs e)
{
cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;
try
{
await TaskAsync(10000, cancellationToken);
}
catch (OperationCanceledException ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
}
}
// キャンセルボタンのクリックイベントハンドラ
private void Cancel_Click(object sender, RoutedEventArgs e)
{
cancellationTokenSource?.Cancel();
}
// 非同期タスク
async Task TaskAsync(int count, CancellationToken cancellationToken)
{
var sum = 0;
await Task.Run(() =>
{
for (int i = 0; i < count; i++)
{
// キャンセルが要求されたら例外をスローするメソッド
cancellationToken.ThrowIfCancellationRequested();
sum += 1;
Debug.WriteLine($"加算結果:{sum}");
}
}, cancellationToken);
}このコードを実行して加算途中でキャンセルボタンをクリックした結果が以下になります。
加算結果:1
加算結果:2
加算結果:3
加算結果:4
例外がスローされました: 'System.OperationCanceledException' (mscorlib.dll の中)
操作は取り消されました。Cancelメソッドを呼びだしたタイミングでOperationCanceledExceptionが発行され、出力結果のように非同期処理がキャンセルされます。
まとめ

この記事では C# の非同期タスクのキャンセル方法について紹介をしました。
C# のCancellationTokenを使うことで、非同期プログラミングにおけるキャンセル機能の実装が可能になります。適切なキャンセル処理を行うことで、リソースリークやアプリケーションのパフォーマンス低下を防ぎます。
ぜひ扱えるようになっておきましょう。



以上、最後まで読んでいただきありがとうございました。

