如何使 Task.WaitAll() 在发生任何异常时中断
本文关键字:任何 异常 中断 Task 何使 WaitAll | 更新日期: 2023-09-27 17:56:26
我想让 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();
更新后,正如@svick在评论中指出的那样,等待任务代理在这里是多余的。他提出了一个改进版本:https://gist.github.com/svick/9992598。
一种方法是使用 CancelTokenSource。您创建 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.
}
并行类可以为您完成这项工作。您可以使用 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);
}
}
}
如果其中一个任务引发异常,则不会执行其他任务,但已开始执行的任务除外。ForEach和Invoke正在等待所有任务完成,然后再恢复对调用代码的控制。如果您使用 ParallelLoopState.IsExceptional,您甚至可以进行更精细的控制。Parallel.Invoke 更适合您的情况。
我想
建议对上面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;
}
}