如何等待一系列任务并停止等待第一个异常? [英] How can I await an array of tasks and stop waiting on first exception?

查看:78
本文介绍了如何等待一系列任务并停止等待第一个异常?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一系列任务,并且正在等待 Task.WhenAll 。我的任务经常失败,在这种情况下,我会向用户显示一个消息框,以便她可以重试。我的问题是报告错误的时间延迟到所有任务完成为止。相反,我想在第一个任务抛出异常后立即通知用户。换句话说,我想要一个快速失败的 Task.WhenAll 版本。由于不存在这样的内置方法,因此我尝试创建自己的方法,但是我的实现未达到我想要的方式。这是我的想法:

I have an array of tasks and I am awaiting them with Task.WhenAll. My tasks are failing frequently, in which case I inform the user with a message box so that she can try again. My problem is that reporting the error is delayed until all tasks are completed. Instead I would like to inform the user as soon as the first task has thrown an exception. In other words I want a version of Task.WhenAll that fails fast. Since no such build-in method exists I tried to make my own, but my implementation does not behave the way I want. Here is what I came up with:

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);
}

这通常比本地 Task的抛出速度更快。 ,但通常不够快。在任务#1完成之前将不会观察到有故障的任务#2。我如何改进它,使其尽快失效?

This generally throws faster than the native Task.WhenAll, but usually not fast enough. A faulted task #2 will not be observed before the completion of task #1. How can I improve it so that it fails as fast as possible?

更新:取消,这不是我现在的要求,但是为了保持一致性,第一个被取消的任务应立即停止等待。在这种情况下,从 WhenAllFailFast 返回的合并任务应具有 Status == TaskStatus.Canceled

Update: Regarding cancellation, it is not in my requirements right now, but lets say that for consistency the first cancelled task should stop the awaiting immediately. In this case the combining task returned from WhenAllFailFast should have Status == TaskStatus.Canceled.

说明::取消方案是用户单击 Cancel 按钮以停止任务。并不是要在异常情况下自动取消未完成的任务。

Clarification: Τhe cancellation scenario is about the user clicking a Cancel button to stop the tasks from completing. It is not about cancelling automatically the incomplete tasks in case of an exception.

推荐答案

最好的选择是建立 WhenAllFailFast 方法,使用 TaskCompletionSource 。您可以.ContinueWith()每个输入任务都具有同步延续,当任务以Faulted状态结束时(使用相同的异常对象),该错误会使TCS出错。

Your best bet is to build your WhenAllFailFast method using TaskCompletionSource. You can .ContinueWith() every input task with a synchronous continuation that errors the TCS when the tasks end in the Faulted state (using the same exception object).

也许例如(未完全测试):

Perhaps something like (not fully tested):

using System;
using System.Threading;
using System.Threading.Tasks;

namespace stackoverflow
{
    class Program
    {
        static async Task Main(string[] args)
        {

            var cts = new CancellationTokenSource();
            cts.Cancel();
            var arr = await WhenAllFastFail(
                Task.FromResult(42),
                Task.Delay(2000).ContinueWith<int>(t => throw new Exception("ouch")),
                Task.FromCanceled<int>(cts.Token));

            Console.WriteLine("Hello World!");
        }

        public static Task<TResult[]> WhenAllFastFail<TResult>(params Task<TResult>[] tasks)
        {
            if (tasks is null || tasks.Length == 0) return Task.FromResult(Array.Empty<TResult>());

            // defensive copy.
            var defensive = tasks.Clone() as Task<TResult>[];

            var tcs = new TaskCompletionSource<TResult[]>();
            var remaining = defensive.Length;

            Action<Task> check = t =>
            {
                switch (t.Status)
                {
                    case TaskStatus.Faulted:
                        // we 'try' as some other task may beat us to the punch.
                        tcs.TrySetException(t.Exception.InnerException);
                        break;
                    case TaskStatus.Canceled:
                        // we 'try' as some other task may beat us to the punch.
                        tcs.TrySetCanceled();
                        break;
                    default:

                        // we can safely set here as no other task remains to run.
                        if (Interlocked.Decrement(ref remaining) == 0)
                        {
                            // get the results into an array.
                            var results = new TResult[defensive.Length];
                            for (var i = 0; i < tasks.Length; ++i) results[i] = defensive[i].Result;
                            tcs.SetResult(results);
                        }
                        break;
                }
            };

            foreach (var task in defensive)
            {
                task.ContinueWith(check, default, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
            }

            return tcs.Task;
        }
    }
}

修改:取消包装AggregateException,取消支持,返回结果数组。防御数组突变,null和empty。明确的TaskScheduler。

Edit: Unwraps AggregateException, Cancellation support, return array of results. Defend against array mutation, null and empty. Explicit TaskScheduler.

这篇关于如何等待一系列任务并停止等待第一个异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆