非同期処理について、ちょっと書いてみます。題材は「コンソール入力されたデータの処理を非同期で行い結果を表示する」ということで。
まずは、非同期処理が終わった時に行う処理をコールバックとして設定する例。
ふつうは 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)」への1件のフィードバック