非同期処理(その3)

前回は、Task クラスの ContinueWith メソッドを利用した「コンソール入力されたデータの処理を非同期で行い結果を表示する」プログラムを書いてみましたが、今回はこのパターンを Visual Studio 2012 で導入された async/await で簡単に書けるかを考えてみます。

実行してみた結果は次のとおりです。
実行画面

結論としては、このパターンは async/await で書き換えることができますが、ちょっと気持ち悪い書き方になってしまいます。綺麗に書くためには、コンソールからのデータ入力部と入力されたデータの処理部を疎結合にする必要がありますが、そのパターンは(その5)で書く予定です。

それでは書き換えてみたプログラムを。

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

// async await を使用。。。Main メソッドのコードが気持ち悪い。
// doAsync メソッドで OperationCanceledException をキャッチしているにもかかわらず、
// Main メソッドに AggregateException が投げられてくる

namespace ManagedTasksTest3
{
    class Program
    {
        // 生成されて終了していない Task オブジェクトを保持する
        private static List<Task> _tasks = new List<Task>();
        private static object _syncObject = new object();

        static void Main(string[] args)
        {
            var cts = new CancellationTokenSource();

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

                if (input == "]") break;

                // 入力と処理を非同期にするために Task 型の task で受け取っているが、この変数はどこでも
                // 使わない。task での受け取りを行わないと、非同期処理のコードが生成されない。
                var task = doAsync(input, cts.Token);
            }

            // キャンセル要求を伝える
            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 だったら例外を除去する(ほかの例外は再スローされる)
                    if (ex is OperationCanceledException)
                    {
                        var message = string.Format("Main メソッドでキャッチされました: {0}", ex.Message);
                        Console.WriteLine(message);
                        result = true;
                    }
                    return result;
                });
            }

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

        static async Task doAsync(string s, CancellationToken token)
        {
            // task は Task<string> 型
            var task = doSomethingAsync(s, token);
            lock (_syncObject)
            {
                _tasks.Add(task);
            }
            try
            {
                // result は string 型
                var result = await task;
                Console.WriteLine(result);
            }
            catch (OperationCanceledException e)
            {
                Console.WriteLine(e.Message);
            }
            finally
            {
                lock (_syncObject)
                {
                    _tasks.Remove(task);
                }
            }
        }

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

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

コメントに書いたとおり、Main メソッドのコードがちょっと気持ち悪い書き方になっていたり、doAsync メソッドの中でキャッチしているはずの OperationCanceledException が Main メソッドに投げられてきたり(実行画面のとおり)しています。

どうすれば綺麗な書き方ができるのかはチョット脇において、次回は同時実行スレッド数の制限を考えてみます(こちらでも同根の問題が発生します)。


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


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

コメントを残す

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