読者です 読者をやめる 読者になる 読者になる

【C#】タスクのキャンセル方法

普段C#ではほんとスクリプトみたいなちまーっとしたものしか作らないので、たまにTaskのキャンセル処理を入れようとするとどうやるのか忘れてしまっています。

と言うわけでメモしておきます。

CancellationTokenSourceを作成する

何はともあれCancellationTokenSourceを作成します。

Taskに指定するCancellationTokenはこのTokenプロパティが持っています。

また、実際にキャンセルするメッセージを発行するのはCancellationTokenではなくCancellationTokenSourceの方です。ちょっとコードを書いてみましょう。


private CancellationTokenSource _tokenSource = null;

private void btnStart_Click(object sender, EventArgs e)
{
    if(_tokenSource == null) _tokenSource = new CancellationTokenSource();
    var token = _tokenSource.Token;
    
    Task.Factory.StartNew(() =>
    {
        // TODO:非同期処理
        
        
    }, token).ContinueWith(t =>
    {
        // TODO:あとしまつ
        _tokenSource.Dispose();
        _tokenSource = null;
        
        
    });
}

private void btnStop_Click(object sender, EventArgs e)
{
    if(_tokenSource != null) _tokenSource.Cancel();
}

キャンセル通知を受けて処理を分岐させる

CancellationTokenSource.Cancelメソッドを使うとCancellationTokenに通知が飛びます。飛んでいったからといって、何がどうなるわけでもないです。非同期処理中に何度かIsCancellationRequestedプロパティを見てやる必要があります。

Task.Factory.StartNew(() =>
{
    // TODO:非同期処理
    
    if (token.IsCancellationRequested)
    {
        // TODO:キャンセル処理
        return;
    }
    
    // TODO:続きの処理
    
    if (token.IsCancellationRequested)
    {
        // TODO:キャンセル処理
        return;
    }
    
}, token).ContinueWith(t =>
{
    // TODO:あとしまつ
    _tokenSource.Dispose();
    _tokenSource = null;
    
    
});

とは言え、これだと流石に面倒です。

キャンセル処理をContinueWithでまとめてやりたい場合は、CancellationToken.ThrowIfCancellationRequestedメソッドを使うと楽です。

Task.Factory.StartNew(() =>
{
    // TODO:非同期処理
    
    // キャンセル通知が来てたら例外を投げてタスクを終了させる
    token.ThrowIfCancellationRequested();
    
    // TODO:続きの処理
    
    token.ThrowIfCancellationRequested();
    
}, token).ContinueWith(t =>
{
    // TODO:あとしまつ
    _tokenSource.Dispose();
    _tokenSource = null;
    
    if (t.IsCanceled)
    {
        // TODO:キャンセルされたときの処理
    }
});

それすら面倒だと思ったらCancellationTokenSource.Cancel(Boolean)の方を使いましょう。


private CancellationTokenSource _tokenSource = null;

private void btnStart_Click(object sender, EventArgs e)
{
    if(_tokenSource == null) _tokenSource = new CancellationTokenSource();
    var token = _tokenSource.Token;
    
    Task.Factory.StartNew(() =>
    {
        // TODO:非同期処理
        
        
    }, token).ContinueWith(t =>
    {
        // TODO:あとしまつ
        _tokenSource.Dispose();
        _tokenSource = null;
        
        if (t.IsCanceled)
        {
            // TODO:キャンセルされたときの処理
        }
    });
}

private void btnStop_Click(object sender, EventArgs e)
{
    // 即例外を投げてキャンセルさせる
    if(_tokenSource != null) _tokenSource.Cancel(true);
}

TaskCreationOptionsと併用するときの注意

タスクにCancellationTokenだけでなく、TaskCreationOptionsも設定したい場合はちょっとした注意が必要です。

と言っても、大したことではないです。TaskFactoryのStartNewメソッドにCancellationTokenとTaskCreationOptionsを同時に受け付けるオーバーロードがないだけです。

Taskのコンストラクタにはあるのに何でないんだ、って感じですが、もしStartNewを使いたい場合はこのオーバーロードを使いましょう。

Task.Factory.StartNew(() =>
{
    // TODO:非同期処理
    
    
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

StartNewメソッドはやたらとオーバーロードが多い上に第二引数にobjectを受け取っちゃうオーバーロードが多数存在するので、引数の順番を間違えてハマらないようにしましょう。

まとめ

とまぁ、まとめてみると大した量じゃないんですが、結構色んなとこに情報が散らばってしまっているのでまとめてみました。