TPL等待特定返回值的任务完成

9
我想请求X个不同的Web服务,并且每个服务都会返回truefalse
这些任务应该并行执行,我想等待第一个完成并返回true的任务。当我收到一个true值时,我不希望等待其他任务完成。
在下面的示例中,由于t3首先完成并返回true,因此不应等待t1t1不需要使用await
var t1 = Task.Run<bool>(() =>
{
    Thread.Sleep(5000);
    Console.WriteLine("Task 1 Excecuted");
    return true;
}, cts.Token);

var t2 = Task.Run<bool>(() =>
{
    Console.WriteLine("Task 2 Executed");
    return false;
}, cts.Token);

var t3 = Task.Run<bool>(() =>
{
    Thread.Sleep(2000);
    Console.WriteLine("Task 3 Executed");
    return true;
}, cts.Token);

基本上我正在寻找带有谓词的Task.WhenAny,当然这是不存在的。


1
https://dev59.com/AGUq5IYBdhLWcg3wKtbs - Adam Plocher
@AdamPlocher,在async-await环境下,那个问题不太相关。 - i3arnon
我感觉这里有一个TPL DataFlow解决方案,但愿我有时间坐下来玩一下它! - Adam Houldsworth
2个回答

17
您可以简单地多次使用 Task.WhenAny 和一个谓词,直到“正确”的任务出现。
async Task<T> WhenAny<T>(IEnumerable<Task<T>> tasks, Func<T, bool> predicate)
{
    var taskList = tasks.ToList();
    Task<T> completedTask = null;
    do
    {
        completedTask = await Task.WhenAny(taskList);
        taskList.Remove(completedTask);
    } while (!predicate(await completedTask) && taskList.Any());

    return completedTask == null ? default(T) : await completedTask;
}

8

有一种选择是使用Reactive Extensions。假设您有一个任务集合。它可以是您在问题中提到的任务:

var tasks = new[] { t1, t2, t3 };

要并行执行任务,并在第一个任务返回 true 时返回结果,您可以使用以下表达式:

var result = await tasks
  .Select(t => t.ToObservable())
  .Merge()
  .FirstOrDefaultAsync(success => success);

将任务转换为可观察序列,每个序列在任务完成时"触发"一次。然后将这些序列合并成一个单一的序列,再使用谓词"转换"回可以使用的内容。如果需要,您可以使用更复杂的谓词而不是success => success

完成此操作后,如果正在使用CancellationTokenSource,则可以取消剩余未完成的任务:

cts.Cancel();

变量 result 现在将是 truefalse,并且任何剩余的任务都已收到取消信号。
如果您想使用样本任务进行测试,您需要稍微修改它们,使用 Task.Delay 而不是 Thread.Sleep 以启用取消任务:
var t1 = Task.Run<bool>(async () =>
{
    await Task.Delay(TimeSpan.FromSeconds(1), cts.Token);
    Console.WriteLine("Task 1 Excecuted");
    return false;
}, cts.Token);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接