非同期処理(その1)

非同期処理について、ちょっと書いてみます。題材は「コンソール入力されたデータの処理を非同期で行い結果を表示する」ということで。
まずは、非同期処理が終わった時に行う処理をコールバックとして設定する例。
ふつうは Task クラスの ContinueWith メソッドを使いますが、まぁこんな実装もあるかなということで 😛
実行画面は次のようになります。
実行画面

プログラムは次のとおりです。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace ManagedTasksTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // 生成されて終了していない Task オブジェクトを保持する
            var tasks = new List<Task>();
            var cts = new CancellationTokenSource();
            var syncObject = new object();

            while (true)
            {
                Console.WriteLine("入力してください(終了: \"]\"キー)。");
                var input = Console.ReadLine();

                if (input == "]") break;

                var task = new Task(() => doSomething(input, cts.Token));
                lock (syncObject)
                {
                    tasks.Add(task);
                }
                callback = (s, token) =>
                {
                    Console.WriteLine(s);
                    if (!token.IsCancellationRequested)
                    {
                    }
                    lock (syncObject)
                    {
                        tasks.Remove(task);
                    }
                };
                task.Start();
            }

            // キャンセル要求を伝える
            cts.Cancel();

            Console.WriteLine("非同期処理の終了を待ちます。");
            Task[] taskArray;
            lock (syncObject)
            {
                taskArray = tasks.ToArray();
            }
            Task.WaitAll(taskArray);

            Console.WriteLine("Enter キー押下で終了します。");
            Console.ReadLine();
        }

        // string と CancellationToken を引数とするデリゲート
        static Action<string, CancellationToken> callback { get; set; }

        static void doSomething(string s, CancellationToken token)
        {
            try
            {
                Thread.Sleep(2500);
                token.ThrowIfCancellationRequested();
                Thread.Sleep(2500);

                var result = string.Format("retrun: {0}", s);

                if (callback != null)
                {
                    callback(result, token);
                }
            }
            catch (OperationCanceledException e)
            {
                Console.WriteLine(string.Format("{0}: 処理中のデータ({1})", e.Message, s));
            }
        }
    }
}

コメントを入れていますし、短いので特に説明はいらないと思いますが、タクスのキャンセルについてだけ、簡単に説明しておきます(外部へのリンクですまそうとしたら、適当なページが見つからなかった 🙁 )。

.NET Framework 4.0 から CancellationToken を利用したタスクのキャンセル機構が導入されました。利用する際には、キャンセル要求を行う側(タスクを起動する側)が CancellationTokenSource クラスのインスタンスを生成し、タスクに CancellationTokenSource の Token プロパティで取得する CancellationToekn を渡しておきます(CancellationToken は構造体なので、値渡しになります)。

キャンセル要求を行うときには、CancellationTokenSource の Cancel メソッドを利用することで、CancellationToken の IsCancellationRequested プロパティが True になります。ここで、Task クラスで実行中の処理に割り込みなどが入らないことに注意です。能動的にキャセルが要求されたことが通知されないので、Task クラスで実行中の処理側で、適当なタイミングで CancellationToken の IsCancellationRequested プロパティの値を確認しないと、中断できません(MS の説明では「処理中のタスク側の事情に無頓着な中断は問題があるので、この方法を採用した」とのことで、それはそのとおりです)。

この確認 & 中断を行うために、CancellationToken クラスに ThrowIfCancellationRequested メソッドが実装されていて、「IsCancellationRequested プロパティの値を確認して、キャンセルが要求されていたら OperationCanceledException 例外を投げる」ことができます(OperationCanceledException 例外が投げられてキャッチされずにスレッドが終了すると、Task の Status プロパティが TaskStatus.Canceled に遷移します)。

投げられた OperationCanceledException 例外のキャッチは当該メソッド内で行うか、OperationCanceledException 例外を投げたスレッドを対象とした Wait 系のメソッドでジョインされるスレッド側で行う必要があります(もちろん、例外を投げずにループなどからブレイクして正常終了させるのもありです)。

次回は、Task クラスの ContinueWith メソッドを使ったものを書いてみます。


非同期処理(インデックス)


1 thought on “非同期処理(その1)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です