前回は、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 メソッドに投げられてきたり(実行画面のとおり)しています。
どうすれば綺麗な書き方ができるのかはチョット脇において、次回は同時実行スレッド数の制限を考えてみます(こちらでも同根の問題が発生します)。
「非同期処理(その3)」への2件のフィードバック