プロセス

[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); // 処理の模擬
    }
}

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

まとめ

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

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

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

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

関連記事

Back to top button