C# 我如何等待一系列任务,并停止等待第一个异常?

C# 我如何等待一系列任务,并停止等待第一个异常?,c#,.net,async-await,task-parallel-library,C#,.net,Async Await,Task Parallel Library,我有一系列的任务,我正在等待它们。我的任务经常失败,在这种情况下,我会用一个消息框通知用户,以便她可以重试。我的问题是报告错误会延迟到所有任务完成。相反,我希望在第一个任务抛出异常后立即通知用户。换句话说,我想要一个快速失败的Task.whalll版本。由于不存在这样的内置方法,我尝试创建自己的方法,但是我的实现没有按照我想要的方式运行。以下是我的想法: public static async Task<TResult[]> WhenAllFailFast<TResult>

我有一系列的任务,我正在等待它们。我的任务经常失败,在这种情况下,我会用一个消息框通知用户,以便她可以重试。我的问题是报告错误会延迟到所有任务完成。相反,我希望在第一个任务抛出异常后立即通知用户。换句话说,我想要一个快速失败的
Task.whalll
版本。由于不存在这样的内置方法,我尝试创建自己的方法,但是我的实现没有按照我想要的方式运行。以下是我的想法:

public static async Task<TResult[]> WhenAllFailFast<TResult>(
    params Task<TResult>[] tasks)
{
    foreach (var task in tasks)
    {
        await task.ConfigureAwait(false);
    }
    return await Task.WhenAll(tasks).ConfigureAwait(false);
}
公共静态异步任务(
参数任务[]任务)
{
foreach(任务中的var任务)
{
等待任务。配置等待(false);
}
返回wait Task.WhenAll(tasks).configurewait(false);
}
这通常比本机的
任务抛出得更快。虽然所有的
,但通常不够快。在任务1完成之前,不会观察到出现故障的任务2。我如何改进它,使它尽可能快地失败


更新:关于取消,我现在不需要它,但是为了一致性,第一个被取消的任务应该立即停止等待。在这种情况下,从
WhenAllFailFast
返回的组合任务应具有
状态==TaskStatus.cancelled


澄清:取消场景是关于用户单击取消按钮停止任务完成。它不是在出现异常时自动取消未完成的任务。

您最好的选择是使用构建您的
whallfailfast
方法。您可以使用.ContinueWith()将每个输入任务同步延续,当任务以故障状态结束时(使用相同的异常对象),会导致TCS出错

可能类似(未完全测试):

使用系统;
使用系统线程;
使用System.Threading.Tasks;
命名空间堆栈溢出
{
班级计划
{
静态异步任务主(字符串[]args)
{
var cts=新的CancellationTokenSource();
cts.Cancel();
var arr=等待FastFail失败(
Task.FromResult(42),
Task.Delay(2000).ContinueWith(t=>抛出新异常(“哎哟”),
Task.fromCancelled(cts.Token));
控制台。WriteLine(“你好,世界!”);
}
FastFail时的公共静态任务(参数任务[]任务)
{
if(tasks为null | | tasks.Length==0)返回Task.FromResult(Array.Empty());
//防御性副本。
var defensive=tasks.Clone()作为任务[];
var tcs=new TaskCompletionSource();
剩余var=防御长度;
动作检查=t=>
{
开关(t状态)
{
案例任务状态。出现故障:
//我们“尝试”,因为其他任务可能会把我们击垮。
tcs.TrySetException(t.Exception.InnerException);
打破
案例任务状态。已取消:
//我们“尝试”,因为其他任务可能会把我们击垮。
tcs.trysetconceled();
打破
违约:
//我们可以安全地在这里设置,因为没有其他任务需要运行。
if(联锁减量(剩余参考)==0)
{
//将结果放入数组中。
var结果=新的TResult[defensive.Length];
对于(var i=0;i

编辑:展开聚合异常、取消支持、返回结果数组。防御数组变异,空和空。显式TaskScheduler。

您的循环以伪串行方式等待每个任务,因此它会在检查task2是否失败之前等待task1完成

您可能会发现本文对第一次失败后中止的模式很有帮助:

公共静态异步任务(
参数任务[]任务)
{
var taskList=tasks.ToList();
而(taskList.Count>0)
{
var task=await task.WhenAny(任务列表).ConfigureAwait(false);
if(task.Exception!=null)
{
//留给读者作为练习:
//正确打开AggregateException;
//处理例外情况;
//取消其他正在运行的任务。
抛出task.Exception.InnerException;
}
任务列表。删除(任务);
}
返回wait Task.WhenAll(tasks).configurewait(false);
}

我最近再次需要
WhenAllFailFast
方法,我修改了@ZaldronGG的方法,使其性能更高(并且更符合Stephen Cleary的建议)。下面的实现在我的PC中每秒处理大约3500000个任务

public static Task<TResult[]> WhenAllFailFast<TResult>(params Task<TResult>[] tasks)
{
    if (tasks is null) throw new ArgumentNullException(nameof(tasks));
    if (tasks.Length == 0) return Task.FromResult(new TResult[0]);

    var results = new TResult[tasks.Length];
    var remaining = tasks.Length;
    var tcs = new TaskCompletionSource<TResult[]>(
        TaskCreationOptions.RunContinuationsAsynchronously);

    for (int i = 0; i < tasks.Length; i++)
    {
        var task = tasks[i];
        if (task == null) throw new ArgumentException(
            $"The {nameof(tasks)} argument included a null value.", nameof(tasks));
        HandleCompletion(task, i);
    }
    return tcs.Task;

    async void HandleCompletion(Task<TResult> task, int index)
    {
        try
        {
            var result = await task.ConfigureAwait(false);
            results[index] = result;
            if (Interlocked.Decrement(ref remaining) == 0)
            {
                tcs.TrySetResult(results);
            }
        }
        catch (OperationCanceledException)
        {
            tcs.TrySetCanceled();
        }
        catch (Exception ex)
        {
            tcs.TrySetException(ex);
        }
    }
}
publicstatictask WhenAllFailFast(params Task[]tasks)
{
如果(tasks为null)抛出新ArgumentNullException(nameof(tasks));
if(tasks.Length==0)返回Task.FromResult(new-TResult[0]);
var results=new TResult[tasks.Length];
var剩余=任务。长度;
var tcs=新任务完成源(
TaskCreationOptions.RunContinuationsAsynchronously);
用于(int i)=
public static Task<TResult[]> WhenAllFailFast<TResult>(params Task<TResult>[] tasks)
{
    if (tasks is null) throw new ArgumentNullException(nameof(tasks));
    if (tasks.Length == 0) return Task.FromResult(new TResult[0]);

    var results = new TResult[tasks.Length];
    var remaining = tasks.Length;
    var tcs = new TaskCompletionSource<TResult[]>(
        TaskCreationOptions.RunContinuationsAsynchronously);

    for (int i = 0; i < tasks.Length; i++)
    {
        var task = tasks[i];
        if (task == null) throw new ArgumentException(
            $"The {nameof(tasks)} argument included a null value.", nameof(tasks));
        HandleCompletion(task, i);
    }
    return tcs.Task;

    async void HandleCompletion(Task<TResult> task, int index)
    {
        try
        {
            var result = await task.ConfigureAwait(false);
            results[index] = result;
            if (Interlocked.Decrement(ref remaining) == 0)
            {
                tcs.TrySetResult(results);
            }
        }
        catch (OperationCanceledException)
        {
            tcs.TrySetCanceled();
        }
        catch (Exception ex)
        {
            tcs.TrySetException(ex);
        }
    }
}