Task.WhenAny
方法的描述表明,它将返回完成的第一个任务,即使它已经出现故障。有没有办法改变这种行为,所以它会返回第一个成功的任务?
答案 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,以确保我们在等待完成的任务时不会死锁。在这种情况下,如果零任务完成,我们永远不会完成此块。
修改强> 我确实略微更改了代码以表示失败的承诺,因此我们可以处理失败的任务。仍然不满意代码,但它是一个开始。