Task.WhenAny用于非故障任务

时间:2018-03-16 04:45:36

标签: c# .net parallel-processing

Task.WhenAny方法的描述表明,它将返回完成的第一个任务,即使它已经出现故障。有没有办法改变这种行为,所以它会返回第一个成功的任务?

2 个答案:

答案 0 :(得分:1)

这样的事情应该这样做(可能需要一些调整 - 未经测试):

private static async Task<Task> WaitForAnyNonFaultedTaskAsync(IEnumerable<Task> tasks)
{
    IList<Task> customTasks = tasks.ToList();
    Task completedTask;
    do
    {
        completedTask = await Task.WhenAny(customTasks);
        customTasks.Remove(completedTask);
    } while (completedTask.IsFaulted && customTasks.Count > 0);

    return completedTask.IsFaulted?null:completedTask;
}

答案 1 :(得分:0)

首先,从我的评论来看,没有直接的方法可以完成此操作而无需等待所有任务完成,然后找到第一个成功运行的任务。

首先,我不确定会导致我未测试过的问题的边缘情况,并且考虑到围绕任务的源代码并且需要超过一个小时的审核,我想开始考虑以下来源码。请在底部查看我的想法。

public static class TaskExtensions
{

    public static async Task<Task> WhenFirst(params Task[] tasks)
    {
        if (tasks == null)
        {
            throw new ArgumentNullException(nameof(tasks), "Must be supplied");
        }
        else if (tasks.Length == 0)
        {
            throw new ArgumentException("Must supply at least one task", nameof(tasks));
        }

        int finishedTaskIndex = -1;
        for (int i = 0, j = tasks.Length; i < j; i++)
        {
            var task = tasks[i];
            if (task == null)
                throw new ArgumentException($"Task at index {i} is null.", nameof(tasks));

            if (finishedTaskIndex == -1 && task.IsCompleted && task.Status == TaskStatus.RanToCompletion)
            {
                finishedTaskIndex = i;
            }
        }

        if (finishedTaskIndex == -1)
        {
            var promise = new TaskAwaitPromise(tasks.ToList());
            for (int i = 0, j = tasks.Length; i < j; i++)
            {
                if (finishedTaskIndex == -1)
                {
                    var taskId = i;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
                    //we dont want to await these tasks as we want to signal the first awaited task completed.
                    tasks[i].ContinueWith((t) =>
                    {
                        if (t.Status == TaskStatus.RanToCompletion)
                        {
                            if (finishedTaskIndex == -1)
                            {
                                finishedTaskIndex = taskId;
                                promise.InvokeCompleted(taskId);
                            }

                        }
                        else
                            promise.InvokeFailed();
                    });
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed

                }

            }
            return await promise.WaitCompleted();
        }


        return Task.FromResult(finishedTaskIndex > -1 ? tasks[finishedTaskIndex] : null);
    }

    class TaskAwaitPromise
    {
        IList<Task> _tasks;
        int _taskId = -1;
        int _taskCount = 0;
        int _failedCount = 0;


        public TaskAwaitPromise(IList<Task> tasks)
        {
            _tasks = tasks;
            _taskCount = tasks.Count;
            GC.KeepAlive(_tasks);
        }

        public void InvokeFailed()
        {
            _failedCount++;
        }

        public void InvokeCompleted(int taskId)
        {
            if (_taskId < 0)
            {
                _taskId = taskId;
            }
        }

        public async Task<Task> WaitCompleted()
        {
            await Task.Delay(0);

            while (_taskId < 0 && _taskCount != _failedCount)
            {

            }
            return _taskId > 0 ? _tasks[_taskId] : null;
        }

    }

}

我理解的代码很冗长,并且可能有很多问题,但是概念是你需要并行执行所有任务并找到第一个成功完成的任务。

如果我们认为我们需要对所有任务进行连续阻止,并且能够从连续块返回到原始调用者。我的主要关注点(除了我不能删除延续的事实)是代码中的while()循环。可能最好添加一些CancellationToken和/或Timeout,以确保我们在等待完成的任务时不会死锁。在这种情况下,如果零任务完成,我们永远不会完成此块。

修改 我确实略微更改了代码以表示失败的承诺,因此我们可以处理失败的任务。仍然不满意代码,但它是一个开始。