非同期処理(その2)

前回の記事ではコールバックを利用した例を書いたので、今回は Task クラスの ContinueWith メソッドを利用した例を書いてみます。
実行画面は次のとおりです。
実行画面

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

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

// 別スレッドで走る doSomething メソッド中で例外をキャッチせず、ジョインされたスレッド側で
// キャッチを行なっているため、デバッグ実行を行うと例外の発生で一時停止します。その場合は
// [継続] ボタンをクリックして続行させてください。「デバッグ無しで開始」させると、途中での停止は
// 発生しません。

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

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

                if (input == "]") break;

                var task = new Task<string>(() => doSomething(input, cts.Token));
                task.ContinueWith((s) => 
                {
                    // task は正常終了したか?
                    if (task.Status == TaskStatus.RanToCompletion)
                    {
                        Console.WriteLine(s.Result);
                    }
                    else
                    {
                        Console.WriteLine("非同期処理が正常終了しませんでした。");
                    }
                    lock (syncObject)
                    {
                        tasks.Remove(task);
                    }
                });
                lock (syncObject)
                {
                    tasks.Add(task);
                }
                task.Start();
            }

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

            Console.WriteLine("非同期処理の終了を待ちます。");
            Task[] taskArray;
            lock (syncObject)
            {
                taskArray = tasks.ToArray();
            }
            try
            {
                Task.WaitAll(taskArray);
            }
            // ジョインされたタスクで発生した例外をキャッチ
            catch (AggregateException ae)
            {
                ae.Handle((ex) =>
                {
                    var result = false;

                    // 例外が OperationCanceledException だったら例外を除去する(ほかの例外は再スローされる)
                    // ただし、この例では doSomething の ContinueWith でセットしている後処理内で
                    // 正常終了しなかったタスクも Remove 対象としているため、WaitAll では検出されない
                    if (ex is OperationCanceledException)
                    {
                        Console.WriteLine(ex.Message);
                        result = true;
                    }
                    return result;
                });
            }

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

        static string doSomething(string s, CancellationToken token)
        {
            Thread.Sleep(2500);
            if (token.IsCancellationRequested)
            {
                var message = string.Format("キャンセルしました: 処理中のデータ({0})", s);
                throw new OperationCanceledException(message);
            }
            Thread.Sleep(2500);

            return string.Format("retrun: {0}", s);
        }
    }
}

前回との違いは、「デリゲートがなくなり、コールバックに登録していた処理を Task クラスの ContinueWith メソッドで登録していること」「doSomething メソッドの型が void から string になったこと」「doSomething メソッドの返却値を s.Result で受け取っていること」ですね。
また、前回の例では別スレッドの実行される doSomething メソッドの中で例外のキャッチを行なっていなしたが、今回の例では ContinueWith で設定する後処理中で正常終了したかどうかの振り分けを行なっているため、doSomething メソッドで例外をキャッチしていません。

次回は、このパターンを Visual Studio 2012 で導入された async/await で書けるかを考えてみます。


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


2 thoughts on “非同期処理(その2)

コメントを残す

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