タスクのキャンセル

CancellationTokenSourceクラス

プロパティ 内容
bool IsCancellationRequested 取り消し (キャンセル) が要求されているかどうか
CancellationToken Token このCancellationTokenSourceに関連付けられているCancellationToken
メソッド 機能
Cancel() キャンセルを要求できる
CancelAfter(Int32) キャンセルするまでの時間を設定できる
   

キャンセルの要求

キャンセルを要求する側はCancellationTokenSource.Cancel()を呼び出し、別スレッドの側はそれのCancellationToken.IsCancellationRequestedでキャンセルの要求を検出し処理を中断します。

CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;

Task.Run(() =>
{
    if (token.IsCancellationRequested)
    {
        // キャンセルを要求されたならば、処理を抜ける
        break;
    }
});

// キャンセルを要求する
tokenSource.Cancel();

一度キャンセルを要求したCancellationTokenSourceはその要求を取り消せないため、再度CancellationTokenSourceが必要なときはそのインスタンスを再生成します。

CancellationTokenSourceの破棄

CancellationTokenSourceへの最後の参照を解放する前に、CancellationTokenSource.Dispose()を呼びます。さもなくばガベージ コレクタがFinalize()を呼ぶまで、そのリソースは解放されません。 Dispose() - CancellationTokenSource.Dispose Method (System.Threading) | Microsoft Learn c# - When to dispose CancellationTokenSource? - Stack Overflow

ただしDispose()の後にCancel()を呼ぶとObjectDisposedExceptionが投げられるため、それへの対処が必要です。

キャンセルの通知

キャンセルしたことを呼び出し側へ通知したいならば、OperationCanceledExceptionを投げることでタスクを終了させます。そうするとStatusプロパティはTaskStatus.Canceledとなり、TaskContinuationOptions.OnlyOnCanceledを指定したContinueWith()で継続タスクを実行できます。

OperationCanceledExceptionはタスクのスレッドから投げられないと捕捉されず、継続タスクも実行されません。

CancellationToken.ThrowIfCancellationRequested()を呼び出してキャンセルさせる方法もありますが、これは

if (token.IsCancellationRequested)
    throw new OperationCanceledException(token);

とすることと同義です。CancellationToken.ThrowIfCancellationRequested Method (System.Threading) | Microsoft Learn

OperationCanceledExceptionが投げられるTaskは、そのコンストラクタでCancellationTokenを渡します。これはキャンセルできたことをTask.IsCanceledで検証するときにも有用ですが、これらが不要ならば渡す必要はありません。c# - Cancellation token in Task constructor: why? - Stack Overflow

public Task (
    Action action,
    System.Threading.CancellationToken cancellationToken
    );
Task(Action, CancellationToken) - Task Constructor (System.Threading.Tasks) | Microsoft Learn

CancellationTokenをTaskのコンストラクタで渡さないとキャンセルと見なされずStatusはFaultedとなり、継続タスクはOnlyOnCanceledではなくOnlyOnFaultedが実行されます。

CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;

Task task = new Task(() =>
{
    token.ThrowIfCancellationRequested();
    // token.IsCancellationRequested が true ならば、
    // OperationCanceledExceptionが投げられる

}, token); // CancellationTokenを渡さねばならない

task.ContinueWith((_task) =>
{
    // キャンセルされたときに、ここが実行される
}, TaskContinuationOptions.OnlyOnCanceled);

時間指定によるキャンセル

CancellationTokenSourceを作成するときに時間を指定すると、一定時間後に自動でキャンセルが要求されるようにできます。

指定の時間でキャンセルが要求されるだけで、その時間でキャンセルされるわけではありません。

public CancellationTokenSource (int millisecondsDelay);
CancellationTokenSource(Int32) - CancellationTokenSource Constructor (System.Threading) | Microsoft Learn

コンストラクタの呼び出し時点から秒読みが開始され、指定の時間が経過するとToken.IsCancellationRequestedがtrueに設定されます。この時間は、CancellationTokenSource.CancelAfter()で後から変更できます。

int millisecondsDelay = 100;

CancellationTokenSource tokenSource = new CancellationTokenSource(millisecondsDelay);
CancellationToken token = tokenSource.Token;

Task task = new Task(() =>
{
    if (token.IsCancellationRequested)
    {
    }
});

先行する処理のキャンセル

連続する要求に対し、最後の1つだけを処理する方法を考えます。

CancellationTokenSource tokenSource = null;

void Method()
{
    CancellationTokenSource newTokenSource = new CancellationTokenSource();
    CancellationTokenSource oldTokenSource = Interlocked.Exchange(ref tokenSource, newTokenSource);

    if (oldTokenSource != null)
    {
        oldTokenSource.Cancel(); // キャンセルを要求する
    }

    try
    {
        // 待機する
        await Task.Delay(1000, newTokenSource.Token); // キャンセルが要求されたならばTaskCanceledExceptionが投げられる

        // 何らかの処理
    }
    catch (TaskCanceledException)
    {
    }    
}
Microsoft Learnから検索