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
を使うことで、非同期プログラミングにおけるキャンセル機能の実装が可能になります。適切なキャンセル処理を行うことで、リソースリークやアプリケーションのパフォーマンス低下を防ぎます。
ぜひ扱えるようになっておきましょう。
以上、最後まで読んでいただきありがとうございました。