実行中のタスクのいずれかが例外をスローした場合にTask.WaitAll()をブレークアウトさせたいので、終了するまで60秒待つ必要はありません。どうすればそのような動作を実現できますか? WaitAll()がそれを達成できない場合、他のc#機能または回避策はありますか?
Task task1 = Task.Run(() => throw new InvalidOperationException());
Task task2 = ...
...
try
{
Task.WaitAll(new Task[]{task1, task2, ...}, TimeSpan.FromSeconds(60));
}
catch (AggregateException)
{
// If any exception thrown on any of the tasks, break out immediately instead of wait all the way to 60 seconds.
}
以下は、元のタスクのコードを変更せずにそれを行う必要があります(テストされていません):
static bool WaitAll(Task[] tasks, int timeout, CancellationToken token)
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
var proxyTasks = tasks.Select(task =>
task.ContinueWith(t => {
if (t.IsFaulted) cts.Cancel();
return t;
},
cts.Token,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Current).Unwrap());
return Task.WaitAll(proxyTasks.ToArray(), timeout, cts.Token);
}
障害が発生したタスク(スローしたタスク)のみを追跡することに注意してください。キャンセルされたタスクも追跡する必要がある場合は、次のように変更します。
if (t.IsFaulted || t.IsCancelled) cts.Cancel();
Updated、コメントで@svickが指摘しているように、タスクプロキシの待機はここでは冗長です。彼は改善されたバージョンを提案します: https://Gist.github.com/svick/9992598 。
並列クラスはあなたのために仕事をすることができます。 Parallel.For、ForEach、またはInvokeを使用できます。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Sample_04_04_2014_01
{
class Program
{
public static void Main(string[] args)
{
try
{
Parallel.For(0,20, i => {
Console.WriteLine(i);
if(i == 5)
throw new InvalidOperationException();
Thread.Sleep(100);
});
}
catch(AggregateException){}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
}
これらのタスクの1つが例外をスローした場合、実行がすでに開始されているタスクを除いて、他のタスクは実行されません。 For、ForEachとInvokeは、すべてのタスクが完了するのを待ってから、呼び出し元のコードへの制御を再開します。 ParallelLoopState.IsExceptionalを使用すると、さらに細かい制御を行うことができます。 Parallel.Invokeはあなたのケースにより適しています。
これを行う1つの方法は、CancellationTokenSourceを使用することです。 canceltokensourceを作成し、それを引数としてTask.WaitAllに渡します。アイデアは、タスクをtry/catchブロックでラップし、例外の場合は、canceltokensourceでcancelを呼び出すことです。
これがサンプルコードです
CancellationTokenSource mainCancellationTokenSource = new CancellationTokenSource();
Task task1 = new Task(() =>
{
try
{
throw new Exception("Exception message");
}
catch (Exception ex)
{
mainCancellationTokenSource.Cancel();
}
}, mainCancellationTokenSource.Token);
Task task2 = new Task(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(3));
Console.WriteLine("Task is running");
}, mainCancellationTokenSource.Token);
task1.Start();
task2.Start();
Task.WaitAll(new[] { task1, task2},
6000, // 6 seconds
mainCancellationTokenSource.Token
);
}
catch (Exception ex)
{
// If any exception thrown on any of the tasks, break out immediately instead of wait all the way to 60 seconds.
}
上記のNoseratioの優れた回答に少し変更を加えることを提案したいと思います。私の場合、スローされた元の例外を保持する必要があり、周囲のtry/catchでは、キャンセルされた状態と例外状態を区別します。
public static void WaitUnlessFault( Task[] tasks, CancellationToken token )
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
foreach ( var task in tasks ) {
task.ContinueWith(t =>
{
if ( t.IsFaulted ) cts.Cancel();
},
cts.Token,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Current);
}
try {
Task.WaitAll(tasks, cts.Token);
}
catch ( OperationCanceledException ex ) {
var faultedTaskEx = tasks.Where(t => t.IsFaulted)
.Select(t => t.Exception)
.FirstOrDefault();
if ( faultedTaskEx != null )
throw faultedTaskEx;
else
throw;
}
}