[C#] タスクを停止させるキャンセルトークンの使い方を解説

C#でタスクを停止させるには、CancellationTokenを使用します。

CancellationTokenSourceを作成し、そのTokenプロパティをタスクに渡します。

タスク内では、token.IsCancellationRequestedを定期的にチェックし、キャンセルが要求された場合に処理を中断します。

また、token.ThrowIfCancellationRequested()を使用すると、キャンセルが要求された際に例外をスローしてタスクを停止できます。

キャンセルを要求するには、CancellationTokenSource.Cancel()を呼び出します。

この記事でわかること
  • キャンセルトークンの基本的な使い方
  • タスクのキャンセル実装手順
  • 例外処理の重要性と方法
  • 応用例による実践的な活用法
  • キャンセル後のタスク管理方法

目次から探す

キャンセルトークンとは

キャンセルトークンは、C#における非同期プログラミングでタスクのキャンセルを管理するための仕組みです。

特に、長時間実行される処理や、ユーザーの操作に応じて処理を中断したい場合に役立ちます。

キャンセルトークンは、CancellationTokenCancellationTokenSourceの2つの主要なクラスで構成されています。

CancellationTokenSourceはキャンセルの要求を発行する役割を持ち、CancellationTokenはその要求を受け取る側のタスクに渡されます。

タスクは、キャンセルが要求されたかどうかを確認し、必要に応じて処理を中断することができます。

この仕組みにより、プログラムの応答性を向上させ、リソースの無駄遣いを防ぐことが可能になります。

キャンセルトークンの基本的な使い方

CancellationTokenSourceの作成

CancellationTokenSourceは、キャンセル要求を発行するためのオブジェクトです。

以下のコードでは、CancellationTokenSourceを作成し、CancellationTokenを取得する方法を示します。

using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
    static void Main(string[] args)
    {
        // CancellationTokenSourceの作成
        CancellationTokenSource cts = new CancellationTokenSource();
        
        // CancellationTokenの取得
        CancellationToken token = cts.Token;
        
        // タスクの実行
        Task.Run(() => DoWork(token), token);
        
        // 2秒後にキャンセルを要求
        Thread.Sleep(2000);
        cts.Cancel();
        
        Console.ReadLine();
    }
    static void DoWork(CancellationToken token)
    {
        while (true)
        {
            // キャンセルが要求されたか確認
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("キャンセルが要求されました。");
                break;
            }
            // 何らかの処理を行う
            Console.WriteLine("処理中...");
            Thread.Sleep(500); // 処理の模擬
        }
    }
}
処理中...
処理中...
キャンセルが要求されました。

タスクにCancellationTokenを渡す方法

タスクにCancellationTokenを渡すことで、タスク内でキャンセル要求を受け取ることができます。

上記の例では、DoWorkメソッドCancellationTokenを引数として渡しています。

これにより、タスクはキャンセル要求を確認できるようになります。

IsCancellationRequestedの使い方

IsCancellationRequestedプロパティは、キャンセルが要求されたかどうかを確認するために使用します。

このプロパティがtrueを返す場合、タスクはキャンセルされるべきです。

上記の例では、if (token.IsCancellationRequested)の条件文でキャンセルを確認しています。

ThrowIfCancellationRequestedでの例外処理

ThrowIfCancellationRequestedメソッドを使用すると、キャンセルが要求された場合にOperationCanceledExceptionをスローすることができます。

これにより、タスク内でキャンセル処理を明示的に行うことができます。

以下のコードはその使用例です。

static void DoWorkWithException(CancellationToken token)
{
    for (int i = 0; i < 10; i++)
    {
        // キャンセルが要求された場合、例外をスロー
        token.ThrowIfCancellationRequested();
        
        // 何らかの処理を行う
        Console.WriteLine($"処理中... {i + 1}");
        Thread.Sleep(500); // 処理の模擬
    }
}

このように、ThrowIfCancellationRequestedを使うことで、タスクのキャンセルをより明確に扱うことができます。

タスクのキャンセルを実装する手順

タスクの開始とキャンセルの準備

タスクを開始する前に、CancellationTokenSourceを作成し、CancellationTokenを取得します。

これにより、タスクがキャンセル要求を受け取る準備が整います。

以下のコードは、タスクを開始する準備を示しています。

using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
    static void Main(string[] args)
    {
        // CancellationTokenSourceの作成
        CancellationTokenSource cts = new CancellationTokenSource();
        
        // CancellationTokenの取得
        CancellationToken token = cts.Token;
        
        // タスクの開始
        Task task = Task.Run(() => DoWork(token), token);
        
        // ここでキャンセルの準備が整った
        // 例: ユーザーの入力を待つなど
        Console.WriteLine("タスクを開始しました。キャンセルするにはEnterを押してください。");
        Console.ReadLine();
        
        // キャンセル要求
        cts.Cancel();
        
        // タスクの完了を待つ
        task.Wait();
    }
    static void DoWork(CancellationToken token)
    {
        // タスクの処理内容
    }
}

タスク内でキャンセルをチェックする方法

タスク内では、CancellationTokenを使用してキャンセルが要求されたかどうかを確認します。

IsCancellationRequestedプロパティを使って、キャンセル要求があった場合に適切な処理を行います。

以下のコードはその例です。

static void DoWork(CancellationToken token)
{
    for (int i = 0; i < 10; i++)
    {
        // キャンセルが要求されたか確認
        if (token.IsCancellationRequested)
        {
            Console.WriteLine("キャンセルが要求されました。");
            return; // 処理を中断
        }
        
        // 何らかの処理を行う
        Console.WriteLine($"処理中... {i + 1}");
        Thread.Sleep(500); // 処理の模擬
    }
}

CancellationTokenSource.Cancel()の呼び出し

キャンセル要求は、CancellationTokenSource.Cancel()メソッドを呼び出すことで行います。

このメソッドを実行すると、関連付けられたすべてのCancellationTokenにキャンセル要求が通知されます。

上記の例では、ユーザーがEnterを押すとキャンセルが要求されます。

// キャンセル要求
cts.Cancel();

タスクのキャンセル後の処理

タスクがキャンセルされた後の処理も重要です。

タスク内でキャンセルが要求された場合、適切にリソースを解放したり、後処理を行ったりする必要があります。

以下のコードは、キャンセル後の処理を示しています。

static void DoWork(CancellationToken token)
{
    try
    {
        for (int i = 0; i < 10; i++)
        {
            // キャンセルが要求されたか確認
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("キャンセルが要求されました。");
                return; // 処理を中断
            }
            
            // 何らかの処理を行う
            Console.WriteLine($"処理中... {i + 1}");
            Thread.Sleep(500); // 処理の模擬
        }
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("タスクがキャンセルされました。リソースを解放します。");
        // リソース解放処理
    }
}

このように、タスクのキャンセルを実装する際は、キャンセル要求の確認とその後の処理を適切に行うことが重要です。

キャンセルが発生した場合の例外処理

OperationCanceledExceptionのキャッチ

タスクがキャンセルされた場合、ThrowIfCancellationRequestedメソッドを使用してOperationCanceledExceptionをスローすることができます。

この例外をキャッチすることで、キャンセル処理を適切に行うことができます。

以下のコードは、OperationCanceledExceptionをキャッチする例です。

static void DoWorkWithException(CancellationToken token)
{
    try
    {
        for (int i = 0; i < 10; i++)
        {
            // キャンセルが要求された場合、例外をスロー
            token.ThrowIfCancellationRequested();
            
            // 何らかの処理を行う
            Console.WriteLine($"処理中... {i + 1}");
            Thread.Sleep(500); // 処理の模擬
        }
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("タスクがキャンセルされました。");
    }
}
処理中... 1
処理中... 2
タスクがキャンセルされました。

タスクのキャンセルとTaskCanceledException

タスクがキャンセルされると、TaskオブジェクトはTaskCanceledExceptionをスローします。

この例外は、タスクが正常に完了しなかったことを示します。

タスクを待機する際にこの例外をキャッチすることで、キャンセル処理を行うことができます。

以下のコードはその例です。

static void Main(string[] args)
{
    CancellationTokenSource cts = new CancellationTokenSource();
    CancellationToken token = cts.Token;
    
    Task task = Task.Run(() => DoWorkWithException(token), token);
    
    // 2秒後にキャンセルを要求
    Thread.Sleep(2000);
    cts.Cancel();
    
    try
    {
        task.Wait(); // タスクの完了を待つ
    }
    catch (AggregateException ex)
    {
        foreach (var inner in ex.InnerExceptions)
        {
            if (inner is TaskCanceledException)
            {
                Console.WriteLine("タスクがキャンセルされました。");
            }
        }
    }
}
処理中... 1
処理中... 2
タスクがキャンセルされました。

キャンセル後のリソース解放

タスクがキャンセルされた場合、リソースを適切に解放することが重要です。

finallyブロックを使用して、タスクがキャンセルされたかどうかに関わらず、リソース解放処理を行うことができます。

以下のコードは、キャンセル後のリソース解放の例です。

static void DoWorkWithResourceRelease(CancellationToken token)
{
    try
    {
        for (int i = 0; i < 10; i++)
        {
            token.ThrowIfCancellationRequested();
            Console.WriteLine($"処理中... {i + 1}");
            Thread.Sleep(500); // 処理の模擬
        }
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("タスクがキャンセルされました。");
    }
    finally
    {
        // リソース解放処理
        Console.WriteLine("リソースを解放します。");
    }
}
処理中... 1
処理中... 2
タスクがキャンセルされました。
リソースを解放します。

このように、キャンセルが発生した場合は、例外処理を適切に行い、リソースを解放することが重要です。

これにより、プログラムの安定性と効率性を向上させることができます。

キャンセルトークンを使った応用例

複数タスクの同時キャンセル

複数のタスクを同時に実行し、同時にキャンセルすることができます。

CancellationTokenSourceを共有することで、すべてのタスクに対してキャンセル要求を行うことが可能です。

以下のコードは、複数のタスクを同時に実行し、キャンセルする例です。

using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
    static void Main(string[] args)
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;
        Task task1 = Task.Run(() => DoWork(token), token);
        Task task2 = Task.Run(() => DoWork(token), token);
        // 2秒後にキャンセルを要求
        Thread.Sleep(2000);
        cts.Cancel();
        Task.WaitAll(task1, task2); // すべてのタスクの完了を待つ
    }
    static void DoWork(CancellationToken token)
    {
        while (true)
        {
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("タスクがキャンセルされました。");
                return;
            }
            Console.WriteLine("処理中...");
            Thread.Sleep(500); // 処理の模擬
        }
    }
}
処理中...
処理中...
タスクがキャンセルされました。
タスクがキャンセルされました。

タイムアウトを使ったキャンセル

タイムアウトを設定して、一定時間内に処理が完了しない場合に自動的にキャンセルすることができます。

以下のコードは、タイムアウトを使ったキャンセルの例です。

static void Main(string[] args)
{
    CancellationTokenSource cts = new CancellationTokenSource(3000); // 3秒のタイムアウト
    CancellationToken token = cts.Token;
    Task task = Task.Run(() => DoWork(token), token);
    try
    {
        task.Wait(); // タスクの完了を待つ
    }
    catch (AggregateException ex)
    {
        foreach (var inner in ex.InnerExceptions)
        {
            if (inner is TaskCanceledException)
            {
                Console.WriteLine("タスクがタイムアウトによりキャンセルされました。");
            }
        }
    }
}
処理中...
処理中...
タスクがタイムアウトによりキャンセルされました。

UI操作とキャンセルトークンの連携

UIアプリケーションでは、ユーザーの操作に応じてタスクをキャンセルすることが重要です。

以下のコードは、ボタンをクリックすることでタスクをキャンセルする例です。

// UIフレームワークに依存するため、擬似コードとして示します。
private void CancelButton_Click(object sender, EventArgs e)
{
    cts.Cancel(); // ボタンがクリックされたらキャンセル
}

長時間実行される処理の中断

長時間実行される処理を中断するために、キャンセルトークンを使用することができます。

以下のコードは、長時間実行される処理をキャンセルする例です。

static void LongRunningProcess(CancellationToken token)
{
    for (int i = 0; i < 100; i++)
    {
        if (token.IsCancellationRequested)
        {
            Console.WriteLine("長時間処理がキャンセルされました。");
            return;
        }
        Console.WriteLine($"処理中... {i + 1}");
        Thread.Sleep(100); // 処理の模擬
    }
}

キャンセル可能なループ処理

キャンセル可能なループ処理を実装することで、ユーザーが任意のタイミングで処理を中断できるようにすることができます。

以下のコードは、キャンセル可能なループ処理の例です。

static void CancelableLoop(CancellationToken token)
{
    while (true)
    {
        if (token.IsCancellationRequested)
        {
            Console.WriteLine("ループ処理がキャンセルされました。");
            break;
        }
        Console.WriteLine("ループ処理中...");
        Thread.Sleep(500); // 処理の模擬
    }
}

これらの応用例を通じて、キャンセルトークンを活用することで、非同期処理の柔軟性と応答性を向上させることができます。

よくある質問

タスクがキャンセルされないのはなぜ?

タスクがキャンセルされない理由はいくつか考えられます。

主な原因は以下の通りです。

  • キャンセル要求の確認を行っていない: タスク内でCancellationTokenIsCancellationRequestedプロパティを確認していない場合、キャンセル要求が無視されます。
  • 例外処理が不適切: ThrowIfCancellationRequestedメソッドを使用している場合、例外が適切にキャッチされていないと、キャンセルが正しく処理されません。
  • タスクがブロックされている: タスクが長時間ブロックされている場合、キャンセル要求が処理されないことがあります。

例えば、I/O操作や無限ループなどです。

CancellationTokenを使わないとどうなる?

CancellationTokenを使用しない場合、タスクはキャンセル要求を受け取ることができず、実行を続けます。

これにより、以下のような問題が発生する可能性があります。

  • プログラムの応答性が低下: ユーザーがキャンセルを希望しても、タスクが終了するまで待たなければならず、プログラムがフリーズしたように見えることがあります。
  • リソースの無駄遣い: 不要な処理が続くことで、CPUやメモリなどのリソースが無駄に消費される可能性があります。
  • エラーハンドリングの困難: タスクが終了しないため、エラー処理や後処理が行えず、プログラムの安定性が損なわれることがあります。

キャンセル後にタスクを再開することはできる?

基本的に、キャンセルされたタスクは再開することができません。

タスクがキャンセルされると、その状態はCanceledとなり、再度実行することはできません。

ただし、以下の方法で新しいタスクを開始することは可能です。

  • 新しいTaskを作成する: キャンセル後に新しいタスクを作成し、必要な処理を再実行することができます。
  • 状態管理を行う: タスクの状態を管理し、キャンセルされた場合に再実行するためのロジックを実装することができます。

これにより、タスクの再実行が可能になります。

// 新しいタスクを作成して再実行する
Task newTask = Task.Run(() => DoWork(token), token);

このように、キャンセル後のタスクの再開はできませんが、新しいタスクを作成することで処理を続けることが可能です。

まとめ

この記事では、C#におけるキャンセルトークンの基本的な使い方や、タスクのキャンセルを実装する手順、さらにはキャンセルが発生した場合の例外処理について詳しく解説しました。

また、キャンセルトークンを活用した応用例として、複数タスクの同時キャンセルやタイムアウトを使ったキャンセル、UI操作との連携なども紹介しました。

これらの知識を活用して、非同期プログラミングにおけるタスク管理をより効果的に行うことができるでしょう。

ぜひ、実際のプロジェクトにおいてキャンセルトークンを取り入れ、プログラムの応答性や効率性を向上させてみてください。

当サイトはリンクフリーです。出典元を明記していただければ、ご自由に引用していただいて構いません。

関連カテゴリーから探す

  • URLをコピーしました!
目次から探す