C#で学ぶタスクキャンセレーションの基本と実践

C#の非同期プログラミングにおいて、タスクのキャンセレーションは非常に重要な技術です。プログラムの効率化や安定性を高めるために、適切なタイミングでタスクをキャンセルできることは必須です。本記事では、タスクキャンセレーションの基本概念から具体的な実装方法までを詳しく解説し、応用例やエラー処理についても触れていきます。非同期プログラミングのスキルを向上させるための参考にしてください。

目次

タスクキャンセレーションの基本概念

タスクキャンセレーションとは、プログラムが実行中のタスクを途中で停止させるためのメカニズムです。非同期プログラミングにおいて、ある条件が満たされたときやユーザーが操作をキャンセルしたときに、無駄な計算リソースを消費しないようにするために非常に有用です。タスクキャンセレーションは、リソースの効率的な利用やプログラムの応答性を向上させる重要な技術です。この概念を理解することは、複雑な非同期プログラムを設計する上で不可欠です。

CancellationTokenの使い方

CancellationTokenの基本的な使い方

CancellationTokenは、タスクのキャンセレーションを管理するための基本的なツールです。これを使用することで、プログラムは特定のタイミングでタスクを停止することができます。

CancellationTokenSourceの作成

まず、キャンセレーションを開始するためのCancellationTokenSourceを作成します。これはキャンセルのトリガーを提供します。

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

タスクでのCancellationTokenの使用

次に、タスク内でCancellationTokenを利用して、キャンセルのリクエストをチェックします。これにより、タスクがキャンセルリクエストを受け取ると、適切に終了することができます。

Task.Run(() =>
{
    for (int i = 0; i < 100; i++)
    {
        if (token.IsCancellationRequested)
        {
            Console.WriteLine("タスクがキャンセルされました。");
            break;
        }
        Console.WriteLine(i);
        Thread.Sleep(100); // Simulate work
    }
}, token);

キャンセルのトリガー

必要に応じて、以下のようにしてタスクのキャンセルをトリガーします。

cts.Cancel();

このようにして、CancellationTokenとCancellationTokenSourceを使うことで、タスクのキャンセレーションを効果的に管理することができます。

タスクのキャンセル方法

キャンセルをトリガーする方法

タスクをキャンセルするためには、CancellationTokenSourceのCancelメソッドを呼び出します。これにより、関連するCancellationTokenにキャンセルリクエストが送信されます。

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

// タスクの開始
Task.Run(() =>
{
    for (int i = 0; i < 100; i++)
    {
        if (token.IsCancellationRequested)
        {
            Console.WriteLine("タスクがキャンセルされました。");
            break;
        }
        Console.WriteLine(i);
        Thread.Sleep(100); // 作業をシミュレート
    }
}, token);

// キャンセルのトリガー
cts.Cancel();

キャンセルをチェックする方法

タスク内で定期的にIsCancellationRequestedプロパティをチェックし、キャンセルがリクエストされたかどうかを確認します。このチェックを入れることで、キャンセルリクエストが来た場合に適切にタスクを終了できます。

OperationCanceledExceptionの使用

キャンセルがリクエストされた際に、OperationCanceledExceptionをスローしてタスクを終了することも一般的です。これにより、タスクの実行がより明確にキャンセルされることを示すことができます。

Task.Run(() =>
{
    try
    {
        for (int i = 0; i < 100; i++)
        {
            token.ThrowIfCancellationRequested();
            Console.WriteLine(i);
            Thread.Sleep(100); // 作業をシミュレート
        }
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("タスクがキャンセルされました。");
    }
}, token);

キャンセル後の後処理

タスクがキャンセルされた場合、必要に応じてリソースのクリーンアップや後処理を行います。これにより、システムの状態が適切に維持されます。

これらの方法を使うことで、タスクのキャンセルを効果的に実装し、プログラムの応答性とリソース効率を向上させることができます。

キャンセルを受け付けるタスクの作成

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

キャンセルを受け付けるタスクを作成するためには、タスク内で定期的にCancellationTokenをチェックし、キャンセルリクエストがあった場合に適切に終了するようにします。

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

Task.Run(() =>
{
    for (int i = 0; i < 1000; i++)
    {
        if (token.IsCancellationRequested)
        {
            Console.WriteLine("タスクがキャンセルされました。");
            return; // タスクを終了
        }
        Console.WriteLine($"処理中: {i}");
        Thread.Sleep(100); // 何らかの作業をシミュレート
    }
}, token);

ThrowIfCancellationRequestedの利用

タスク内でThrowIfCancellationRequestedメソッドを使用すると、キャンセルリクエストがあった場合にOperationCanceledExceptionがスローされ、タスクを即座に終了させることができます。

Task.Run(() =>
{
    try
    {
        for (int i = 0; i < 1000; i++)
        {
            token.ThrowIfCancellationRequested();
            Console.WriteLine($"処理中: {i}");
            Thread.Sleep(100); // 何らかの作業をシミュレート
        }
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("タスクがキャンセルされました。");
    }
}, token);

長時間実行されるタスクのキャンセル

長時間実行されるタスクの場合、特にキャンセルを受け付ける設計が重要です。以下は、長時間実行されるタスクにおけるキャンセルの実装例です。

Task.Run(async () =>
{
    try
    {
        for (int i = 0; i < 100; i++)
        {
            token.ThrowIfCancellationRequested();
            Console.WriteLine($"処理中: {i}");
            await Task.Delay(1000, token); // 非同期での遅延処理
        }
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("長時間実行されるタスクがキャンセルされました。");
    }
}, token);

これらの方法により、タスクがキャンセルリクエストを受け付けるように設計することができます。適切なキャンセル処理を行うことで、プログラムの応答性を高め、システムリソースを効率的に使用することができます。

実装例:基本的なキャンセル可能なタスク

基本的なキャンセル可能なタスクの実装

ここでは、基本的なキャンセル可能なタスクの実装例を示します。キャンセルがリクエストされた場合に、タスクが適切に終了するように設計されています。

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 task = Task.Run(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                if (token.IsCancellationRequested)
                {
                    Console.WriteLine("タスクがキャンセルされました。");
                    break;
                }
                Console.WriteLine($"処理中: {i}");
                Thread.Sleep(100); // 作業をシミュレート
            }
        }, token);

        // ユーザーがキャンセルをリクエスト
        Console.WriteLine("タスクをキャンセルするにはEnterキーを押してください...");
        Console.ReadLine();
        cts.Cancel();

        try
        {
            task.Wait(token); // タスクがキャンセルされるのを待つ
        }
        catch (AggregateException ae)
        {
            ae.Handle(e => e is OperationCanceledException);
            Console.WriteLine("タスクのキャンセルが完了しました。");
        }

        Console.WriteLine("プログラムが終了しました。");
    }
}

コードの説明

この例では、以下のステップでキャンセル可能なタスクを実装しています。

  1. CancellationTokenSourceの作成:
   CancellationTokenSource cts = new CancellationTokenSource();
   CancellationToken token = cts.Token;
  1. タスクの実行:
    タスクはTask.Runを使用して実行され、ループ内でキャンセルがリクエストされたかどうかをチェックします。
   Task task = Task.Run(() =>
   {
       for (int i = 0; i < 100; i++)
       {
           if (token.IsCancellationRequested)
           {
               Console.WriteLine("タスクがキャンセルされました。");
               break;
           }
           Console.WriteLine($"処理中: {i}");
           Thread.Sleep(100); // 作業をシミュレート
       }
   }, token);
  1. キャンセルのトリガー:
    ユーザーの入力によってキャンセルをトリガーします。
   Console.WriteLine("タスクをキャンセルするにはEnterキーを押してください...");
   Console.ReadLine();
   cts.Cancel();
  1. タスクの終了待機:
    タスクがキャンセルされるのを待ち、例外処理でOperationCanceledExceptionをハンドルします。
   try
   {
       task.Wait(token); // タスクがキャンセルされるのを待つ
   }
   catch (AggregateException ae)
   {
       ae.Handle(e => e is OperationCanceledException);
       Console.WriteLine("タスクのキャンセルが完了しました。");
   }

この基本的な例を基にして、さらに複雑なシナリオにも対応できるように応用していくことができます。

実装例:複数のタスクのキャンセル

複数タスクのキャンセル方法

複数のタスクを同時にキャンセルする場合も、CancellationTokenを利用して簡単に実装できます。以下は、その具体例です。

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;

        // タスク1
        Task task1 = Task.Run(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                if (token.IsCancellationRequested)
                {
                    Console.WriteLine("タスク1がキャンセルされました。");
                    break;
                }
                Console.WriteLine($"タスク1 処理中: {i}");
                Thread.Sleep(100); // 作業をシミュレート
            }
        }, token);

        // タスク2
        Task task2 = Task.Run(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                if (token.IsCancellationRequested)
                {
                    Console.WriteLine("タスク2がキャンセルされました。");
                    break;
                }
                Console.WriteLine($"タスク2 処理中: {i}");
                Thread.Sleep(100); // 作業をシミュレート
            }
        }, token);

        // ユーザーがキャンセルをリクエスト
        Console.WriteLine("タスクをキャンセルするにはEnterキーを押してください...");
        Console.ReadLine();
        cts.Cancel();

        try
        {
            Task.WaitAll(new[] { task1, task2 }, token); // タスクがキャンセルされるのを待つ
        }
        catch (AggregateException ae)
        {
            ae.Handle(e => e is OperationCanceledException);
            Console.WriteLine("すべてのタスクのキャンセルが完了しました。");
        }

        Console.WriteLine("プログラムが終了しました。");
    }
}

コードの説明

この例では、複数のタスクを同時にキャンセルするための方法を示しています。

  1. CancellationTokenSourceの作成:
   CancellationTokenSource cts = new CancellationTokenSource();
   CancellationToken token = cts.Token;
  1. タスク1とタスク2の実行:
    各タスクはTask.Runを使用して実行され、ループ内でキャンセルがリクエストされたかどうかをチェックします。
   Task task1 = Task.Run(() =>
   {
       for (int i = 0; i < 100; i++)
       {
           if (token.IsCancellationRequested)
           {
               Console.WriteLine("タスク1がキャンセルされました。");
               break;
           }
           Console.WriteLine($"タスク1 処理中: {i}");
           Thread.Sleep(100); // 作業をシミュレート
       }
   }, token);

   Task task2 = Task.Run(() =>
   {
       for (int i = 0; i < 100; i++)
       {
           if (token.IsCancellationRequested)
           {
               Console.WriteLine("タスク2がキャンセルされました。");
               break;
           }
           Console.WriteLine($"タスク2 処理中: {i}");
           Thread.Sleep(100); // 作業をシミュレート
       }
   }, token);
  1. キャンセルのトリガー:
    ユーザーの入力によってキャンセルをトリガーします。
   Console.WriteLine("タスクをキャンセルするにはEnterキーを押してください...");
   Console.ReadLine();
   cts.Cancel();
  1. タスクの終了待機:
    Task.WaitAllを使用して、すべてのタスクがキャンセルされるのを待ち、例外処理でOperationCanceledExceptionをハンドルします。
   try
   {
       Task.WaitAll(new[] { task1, task2 }, token); // タスクがキャンセルされるのを待つ
   }
   catch (AggregateException ae)
   {
       ae.Handle(e => e is OperationCanceledException);
       Console.WriteLine("すべてのタスクのキャンセルが完了しました。");
   }

この例を参考にすることで、複数のタスクを効率的にキャンセルし、システムのリソースを有効活用することができます。

エラー処理とキャンセレーション

キャンセルされたタスクのエラー処理

タスクがキャンセルされた場合、適切にエラーを処理することが重要です。キャンセルが発生すると、OperationCanceledExceptionがスローされるため、これをキャッチして処理します。

基本的なエラー処理の実装例

以下のコード例では、タスクがキャンセルされたときにOperationCanceledExceptionをキャッチし、適切なメッセージを表示します。

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 task = Task.Run(() =>
        {
            try
            {
                for (int i = 0; i < 100; i++)
                {
                    token.ThrowIfCancellationRequested();
                    Console.WriteLine($"処理中: {i}");
                    Thread.Sleep(100); // 作業をシミュレート
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("タスクがキャンセルされました。");
                // 必要に応じて、キャンセル時のリソースクリーンアップを実行
            }
        }, token);

        // キャンセルのトリガー
        Console.WriteLine("タスクをキャンセルするにはEnterキーを押してください...");
        Console.ReadLine();
        cts.Cancel();

        try
        {
            task.Wait(token); // タスクがキャンセルされるのを待つ
        }
        catch (AggregateException ae)
        {
            ae.Handle(e => e is OperationCanceledException);
            Console.WriteLine("タスクのキャンセルが完了しました。");
        }

        Console.WriteLine("プログラムが終了しました。");
    }
}

エラー処理のポイント

  1. キャンセルリクエストのチェック:
    タスク内で定期的にCancellationToken.ThrowIfCancellationRequestedを呼び出すことで、キャンセルがリクエストされた際に即座にOperationCanceledExceptionをスローします。
  2. 例外のキャッチと処理:
    タスク内でOperationCanceledExceptionをキャッチし、キャンセルされたことを示すメッセージを表示します。また、必要に応じてリソースのクリーンアップを行います。
  3. タスクの終了待機と例外処理:
    Task.Waitを使用してタスクが終了するのを待ちますが、キャンセルが発生した場合にAggregateExceptionをキャッチし、OperationCanceledExceptionのみをハンドルします。

キャンセル時のリソースクリーンアップ

キャンセルが発生した場合、リソースを適切にクリーンアップすることが重要です。例えば、ファイルハンドルの解放やメモリの解放などを行います。以下は、キャンセル時にリソースクリーンアップを行う例です。

catch (OperationCanceledException)
{
    Console.WriteLine("タスクがキャンセルされました。");
    // クリーンアップコード
    CleanupResources();
}

これらの方法を使うことで、キャンセルされたタスクのエラー処理を適切に行い、システムの安定性と信頼性を高めることができます。

キャンセル時のリソースクリーンアップ

キャンセル後のリソースクリーンアップの重要性

タスクがキャンセルされた際には、システムリソース(ファイル、メモリ、ネットワーク接続など)を適切に解放することが重要です。これにより、リソースリークを防ぎ、システムのパフォーマンスと安定性を維持できます。

キャンセル時のクリーンアップの実装例

以下は、タスクがキャンセルされた際に、リソースをクリーンアップする具体例です。この例では、ファイルストリームとメモリリソースのクリーンアップを行います。

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;

        Task task = Task.Run(() =>
        {
            FileStream fileStream = null;
            try
            {
                // ファイルの作成と書き込み
                fileStream = new FileStream("example.txt", FileMode.Create, FileAccess.Write);
                byte[] buffer = new byte[1024];

                for (int i = 0; i < 100; i++)
                {
                    token.ThrowIfCancellationRequested();
                    // ファイルにデータを書き込む
                    fileStream.Write(buffer, 0, buffer.Length);
                    Console.WriteLine($"書き込み中: {i}");
                    Thread.Sleep(100); // 作業をシミュレート
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("タスクがキャンセルされました。");
            }
            finally
            {
                // ファイルストリームのクローズ
                if (fileStream != null)
                {
                    fileStream.Close();
                    Console.WriteLine("ファイルストリームがクローズされました。");
                }
                // 他のリソースクリーンアップ
                CleanupResources();
            }
        }, token);

        // キャンセルのトリガー
        Console.WriteLine("タスクをキャンセルするにはEnterキーを押してください...");
        Console.ReadLine();
        cts.Cancel();

        try
        {
            task.Wait(token); // タスクがキャンセルされるのを待つ
        }
        catch (AggregateException ae)
        {
            ae.Handle(e => e is OperationCanceledException);
            Console.WriteLine("タスクのキャンセルが完了しました。");
        }

        Console.WriteLine("プログラムが終了しました。");
    }

    static void CleanupResources()
    {
        // ここにクリーンアップコードを記述
        Console.WriteLine("リソースがクリーンアップされました。");
    }
}

コードの説明

  1. ファイルストリームの作成とクローズ:
    ファイルストリームを作成し、タスクがキャンセルされた場合に必ずクローズされるように、finallyブロックでクローズ処理を行います。
  2. クリーンアップ関数の呼び出し:
    finallyブロック内で、追加のクリーンアップが必要な場合に備えてCleanupResources関数を呼び出します。
  3. キャンセルリクエストのチェック:
    タスク内で定期的にtoken.ThrowIfCancellationRequestedを呼び出し、キャンセルリクエストがあった場合に即座にOperationCanceledExceptionをスローします。
  4. タスクの終了待機と例外処理:
    Task.Waitを使用してタスクが終了するのを待ち、キャンセルが発生した場合にAggregateExceptionをキャッチし、OperationCanceledExceptionのみをハンドルします。

このようにして、タスクがキャンセルされた場合にもシステムリソースを適切にクリーンアップし、安定したプログラムを実装することができます。

応用例:長時間実行されるタスクのキャンセル

長時間実行されるタスクの課題

長時間実行されるタスクは、ユーザーのインタラクションや他のタスクの影響を受けやすいため、キャンセル可能な設計が特に重要です。キャンセル可能な設計により、ユーザーは長時間の待ち時間を避け、システムリソースを効率的に管理することができます。

長時間実行されるタスクの実装例

以下の例は、長時間実行されるタスクをキャンセル可能にする方法を示しています。この例では、キャンセルリクエストをチェックしながら非同期でデータを処理します。

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;

        Task longRunningTask = LongRunningOperationAsync(token);

        // キャンセルのトリガー
        Console.WriteLine("タスクをキャンセルするにはEnterキーを押してください...");
        Console.ReadLine();
        cts.Cancel();

        try
        {
            await longRunningTask; // タスクがキャンセルされるのを待つ
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("長時間実行されるタスクがキャンセルされました。");
        }

        Console.WriteLine("プログラムが終了しました。");
    }

    static async Task LongRunningOperationAsync(CancellationToken token)
    {
        for (int i = 0; i < 100; i++)
        {
            token.ThrowIfCancellationRequested();
            // 長時間かかる処理をシミュレート
            await Task.Delay(1000, token);
            Console.WriteLine($"処理中: {i}");
        }
    }
}

コードの説明

  1. 長時間実行される非同期タスクの定義:
    長時間実行される処理を非同期タスクとして定義し、キャンセルトークンを受け取ります。
   static async Task LongRunningOperationAsync(CancellationToken token)
   {
       for (int i = 0; i < 100; i++)
       {
           token.ThrowIfCancellationRequested();
           // 長時間かかる処理をシミュレート
           await Task.Delay(1000, token);
           Console.WriteLine($"処理中: {i}");
       }
   }
  1. タスクのキャンセルリクエストチェック:
    タスク内で定期的にtoken.ThrowIfCancellationRequestedを呼び出し、キャンセルリクエストがあった場合に即座にOperationCanceledExceptionをスローします。
  2. メインタスクの実行とキャンセル:
    メインタスクを非同期で実行し、ユーザーの入力によってキャンセルをトリガーします。キャンセルリクエストがあると、awaitで待機中のタスクがキャンセルされます。
   Task longRunningTask = LongRunningOperationAsync(token);

   // キャンセルのトリガー
   Console.WriteLine("タスクをキャンセルするにはEnterキーを押してください...");
   Console.ReadLine();
   cts.Cancel();
  1. 例外処理:
    キャンセルが発生した場合にOperationCanceledExceptionをキャッチし、キャンセルされたことを示すメッセージを表示します。
   try
   {
       await longRunningTask; // タスクがキャンセルされるのを待つ
   }
   catch (OperationCanceledException)
   {
       Console.WriteLine("長時間実行されるタスクがキャンセルされました。");
   }

このように、長時間実行されるタスクをキャンセル可能に設計することで、ユーザーが操作を中断できる柔軟性を提供し、システムリソースを効率的に管理できます。

まとめ

タスクキャンセレーションは、C#の非同期プログラミングにおいて重要な技術です。適切なキャンセル処理を実装することで、システムの効率と応答性を大幅に向上させることができます。本記事では、基本概念から実践的な応用例までを通じて、タスクキャンセレーションの方法を詳しく解説しました。CancellationTokenを活用し、エラー処理やリソースクリーンアップを適切に行うことで、より信頼性の高いプログラムを作成することが可能です。今後の開発において、この知識が役立つことを願っています。

コメント

コメントする

目次