【C#】Task(Threadから切換/キャンセル処理)

スポンサーリンク

いっつもThread(スレッド)処理でやってるんだけど、参考にしているソフトがTask(タスク)を使っていたので、いい機会だと思いちょっと作ってみました。

いつも作っているThread処理

いつもスレッド処理を作る時は、2つのThreadを使っています。
1つは、実行処理用。もう一つは、終了処理用です。
イメージとしては下記の感じ。

このイメージでスレッドを作ると下記の感じになる。

using System.Threading;

namespace Test1
{
    public partial class Form1 : Form
    {
        private Thread ThreadA;
        private Thread ThreadB;

        public Form1()
        {
            InitializeComponent();

            ThreadA = new Thread(FunctionExecute);
            ThreadB = new Thread(FunctionComplete);

            ThreadA.Start();
            ThreadB.Start();
        }

        private void FunctionExecute()
        {
            Console.WriteLine("処理開始");
            for (int n = 0;n < 10;n++)
            {
                Thread.Sleep(1000);
                Console.WriteLine((n + 1).ToString() + "秒");
            }

            Console.WriteLine("処理終了");
        }

        private void FunctionComplete()
        {
            do
            {
                Thread.Sleep(100);
            } while (ThreadA != null && ThreadA.IsAlive == true);

            Console.WriteLine("終了処理開始");

            //--- 終了処理 ---

            Console.WriteLine("終了処理終了");
        }
    }
}

出力ウィンドウで結果を確認すると、下記の感じになる。

いい感じにできました。

いつものThread処理をTask処理に置き換えた時

ここから本題のTask処理に変更した場合です。
こんな風になりました。

using System.Threading.Tasks;

namespace Test1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // タスク生成・実行(ContinueWithはタスクが終わった後の処理)
            Task.Run(FunctionExecute()).ContinueWith(t => FunctionCompleteTask());
        }

        private void FunctionExecute()
        {
            Console.WriteLine("処理開始");
            for (int n = 0;n < 10;n++)
            {
                Thread.Sleep(1000);
                Console.WriteLine((n + 1).ToString() + "秒");
            }

            Console.WriteLine("処理終了");
        }

        private void FunctionCompleteTask()
        {
            Console.WriteLine("終了処理開始");

            //--- 終了処理 ---

            Console.WriteLine("終了処理終了");
        }

なんとThreadの時に比べて、数行減らす事が出来ました。
タスクの実行がちょっと特殊な書き方なのでわかりずらいっっ!!
簡単に説明すると、

●Task.Run(FunctionExecute())
  タスクで、FunctionExecuteを実行するよ!
ContinueWith(t => FunctionCompleteTask())
  タスクが終わったら、FunctionCompleteTaskを実行してね!

1行で書くと分かりずらい場合は、2行に分けて書きましょう!!

// タスク生成・実行(ContinueWithはタスクが終わった後の処理)
Task tmpTask = Task.Run(FunctionExecute());
tmpTask.ContinueWith(t => FunctionCompleteTask());

2行に分けて書くと分かりやすいですね。
ContinueWithの引数がAction<Task>で、書き方がこれ以外の書き方が分かりませんでした。

結果は先ほどと変わらないので割愛します。
これで無事にThreadからTaskへの切り替えが完了でした。
イメージ的にはこんな感じになりました。
ソースコードもスッキリしていい感じです。

Taskのキャンセル処理について

キャンセル要求という機能としてあるので、紹介します。
※個人的にはフラグ処理でもいいと思うのですが。。。笑
やりたい事は、下図の通りで、何かのタイミングでCancel要求を出して、Cancel要求を確認したらTaskを終了するという流れになります。

こんな感じのフォームを作ってみました。

まずは、CancellationTokenSouceを定義します。

namespace Test1
{
    public partial class Form1 : Form
    {
        private CancellationTokenSource TaskCanceler;
    (以下、後記)

先ほどの実行処理にCancel要求があった場合を追加します。

private void FunctionExecute()
{
    Console.WriteLine("処理開始");
    for (int n = 0;n < 10;n++)
    {
        Thread.Sleep(1000);
        Console.WriteLine((n + 1).ToString() + "秒");

        if (TaskCanceler.Token.IsCancellationRequested)
        {
            Console.WriteLine("Cancelされました");
            break;
        }
    }

    Console.WriteLine("処理終了");
}

次に終章処理では、CancellationTokenSouceを開放します。

private void FunctionCompleteTask()
{
    Console.WriteLine("終了処理開始");

    //--- 終了処理 ---
    // キャンセルクラスを解放
    if (TaskCanceler != null)
    {
        TaskCanceler.Dispose();
        TaskCanceler = null;
    }

    Console.WriteLine("終了処理終了");
}

あとは、タスクの実行ボタン(button1)の処理。

private void button1_Click(object sender, EventArgs e)
{
    // タスクオブジェがnullなら生成
    if (TaskCanceler == null) TaskCanceler = new CancellationTokenSource();

    // タスク生成・実行(ContinueWithはタスクが終わった後の処理)
    Task tmpTask = Task.Run(FunctionExecute).ContinueWith(t => FunctionCompleteTask());
} 

最後にCancel要求ボタン(button2)の処理。

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

これを実行してみたところ。。。

無事にCancel要求を実施して、タスクを終了させることが出来ました。

まとめ

今回はThreadからの卒業の為にTaskについて記載しました。
調べているとごちゃごちゃ書いていて、正直わかりずらかったのを自分なりにまとめました。

自分がよくやっている【実行処理と終了処理】を分けて実施するのも、Taskではスマートにかけて、ちょっと「なんで早くチャレンジしなかった!!」と後悔です。。。

コメント

タイトルとURLをコピーしました