tamoto先生、utm.先生、TN8001先生に教えて頂きました。
ありがとうございます。
[したい事]
1.button1をクリックすると、非同期処理Asyncメソッドが起動され、そこからtaskA、taskB、taskCが非同期で起動されます。
2.3つのタスクが完了した順に戻り値戻り値を確認し、"OK"が含まれていれば残りの動作中タスクをキャンセルしたい。
のですが、
await Task.WhenAny(taskA, taskB, taskC);
はタスクが一つでも終われば、次に進みますし、
await Task.WhenAll(taskA, taskB, taskC);
で待てば確実ですが、一番遅いタスクに引っ張れます。どうしましょ?
複数のタスクで並行して実行させる「重い処理」はこちら↓
重い処理
static string _重い処理(int count, string ID, CancellationToken token)
{
DateTime now = DateTime.Now;
Console.WriteLine($"ID: {ID} ⇒ 開始 {now:HH:mm:ss.fff}");
//長大処理
for (int i = 0; i < count; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine($"{ID}: キャンセル検知!");
return $"{ID} / キャンセルされました";
}
Thread.Sleep(1000); //1秒停止
}
Console.WriteLine($"{ID}: 処理完了({count}秒)");
string ans = "";
if (ID == "1st") //1stは5秒で完了する。
{
ans = ID + "/NG";
}
else if (ID == "2nd") //2ndは3秒で完了する。
{
ans = ID + "/OK"; //←ここだけOK!これを待ちたい。
}
else if (ID == "3rd") //3rdは1秒で完了する。
{
ans = ID + "/NG";
}
return ans;
}
1.模範解答01
模範解答01
using System.Threading; //CancellationTokenSource利用の為追加要
private async void buttonサンプルA_Click(object sender, EventArgs e)
{
await 非同期処理01Async();
}
private static async Task 非同期処理01Async()
{
//キャンセルトークン設定
var cts = new CancellationTokenSource();
var token = cts.Token;
Task<string> taskA = Task.Run(() => _重い処理(10, "1st", token), token); //10秒
Task<string> taskB = Task.Run(() => _重い処理(5, "2nd", token), token); //5秒
Task<string> taskC = Task.Run(() => _重い処理(1, "3rd", token), token); //1秒
// タスクを実行する **今回のキモです。汎用処理なので関数化するなり改変するなりしてください。
await Task.Run(async () =>
{
var tasks = new List<Task<string>> { taskA, taskB, taskC };
var count = tasks.Count;
var tcs = new TaskCompletionSource<string>(); // 最初にOKを見つけた時点でタスクを解決するため
// 各タスクを並行して実行
foreach (var t in tasks)
{
// タスクごとに処理
var taskTmp = Task.Run(async () =>
{
var result = await t;
count = count - 1;
// 結果に"OK"が含まれていた場合、即時終了
if (result.Contains("OK"))
{
tcs.SetResult(result); // 結果を返す
}
// すべてのタスクが終了したらcountが0になる
if (count <= 0 && !tcs.Task.IsCompleted)
{
tcs.SetResult("Not Found.");
}
});
}
// 結果を待つ
//↓【このreturnについて】Task.Runは関数を引数にとるのでその関数内の戻り値としてreturnを指定している。
return await tcs.Task;
});
// 残りをキャンセル
cts.Cancel();
// それぞれの結果取得
try
{
Console.WriteLine($"taskA: {taskA.Result}");
}
catch (AggregateException e)
{
Console.WriteLine($"taskA: {e.InnerException.Message}");
}
try
{
Console.WriteLine($"taskB: {taskB.Result}");
}
catch (AggregateException e)
{
Console.WriteLine($"taskB: {e.InnerException.Message}");
}
try
{
Console.WriteLine($"taskC: {taskC.Result}");
}
catch (AggregateException e)
{
Console.WriteLine($"taskC: {e.InnerException.Message}");
}
}
2.模範解答02
模範解答02
using System.Threading; //CancellationTokenSource利用の為追加要
private void buttonサンプルB_Click(object sender, EventArgs e)
{
非同期処理02Async();
}
private async void 非同期処理02Async()
{
var cts = new CancellationTokenSource();
var token = cts.Token;
var taskA = Task.Run(() => _重い処理(10, "1st", token), token);
var taskB = Task.Run(() => _重い処理(5, "2nd", token), token);
var taskC = Task.Run(() => _重い処理(1, "3rd", token), token);
var tasks = new List<Task<string>> { taskA, taskB, taskC };
while (0 < tasks.Count)
{
var task = await Task.WhenAny(tasks);
var s = await task;
if (s.Contains("OK"))
{
break;
};
tasks.Remove(task);
}
cts.Cancel();
}