Task.WaitAny()-检查结果

时间:2018-08-30 19:17:46

标签: c# async-await task task-parallel-library cancellationtokensource

我在数组中有一系列任务。如果任务为“良好”,则返回字符串。如果为“不良”:则返回null。

我希望能够并行运行所有任务,一旦第一个任务返回“ Good”,然后取消其他任务并获得“ Good”结果。

我现在正在这样做,但是问题是所有任务都需要运行,然后我遍历它们以寻找第一个好的结果。

List<Task<string>> tasks = new List<Task<string>>();
Task.WaitAll(tasks.ToArray());

4 个答案:

答案 0 :(得分:1)

您可以使用以下示例获得所需的结果。

declare module '*.graphql';

编辑:如果要终止所有任务,可以使用CancellationToken。

有关详细信息,请阅读docs

答案 1 :(得分:1)

  

我希望能够并行运行所有任务,一旦第一个任务返回“ Good”,然后取消其他任务并获得“ Good”结果。

这是一种误解,因为Cancellation in TPL is co-operative,所以一旦启动任务,就无法取消它。 CancellationToken可以在启动Task之前工作,也可以在以后引发异常(如果要求取消),这意味着启动并采取必要的操作,例如从逻辑中抛出自定义异常。

检查以下query,其中列出了许多有趣的答案,但没有一个取消。以下也是一种可能的选择:

public static class TaskExtension<T>
{
  public static async Task<T> FirstSuccess(IEnumerable<Task<T>> tasks, T goodResult)

    {
        // Create a List<Task<T>>
        var taskList = new List<Task<T>>(tasks);
        // Placeholder for the First Completed Task
        Task<T> firstCompleted = default(Task<T>);
        // Looping till the Tasks are available in the List
        while (taskList.Count > 0)
        {
            // Fetch first completed Task
            var currentCompleted = await Task.WhenAny(taskList);

            // Compare Condition
            if (currentCompleted.Status == TaskStatus.RanToCompletion
                && currentCompleted.Result.Equals(goodResult))
            {
                // Assign Task and Clear List
                firstCompleted = currentCompleted;
                break;
            }
            else
               // Remove the Current Task
               taskList.Remove(currentCompleted);
        }
        return (firstCompleted != default(Task<T>)) ? firstCompleted.Result : default(T);
    }
}

用法:

var t1 = new Task<string>(()=>"bad");

var t2 = new Task<string>(()=>"bad");

var t3 = new Task<string>(()=>"good");

var t4 = new Task<string>(()=>"good");

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

foreach(var tt in taskArray)
  tt.Start();

var finalTask = TaskExtension<string>.FirstSuccess(taskArray,"good");

Console.WriteLine(finalTask.Result);

您甚至可以返回Task<Task<T>>而不是Task<T>来进行必要的逻辑处理

答案 2 :(得分:1)

我正在研究Task.WhenAny(),它将在第一个“完成”任务上触发。不幸的是,从这个意义上说,已完成的任务基本上是任何东西……甚至一个例外也被视为“已完成”。据我所知,没有其他方法可以检查您所谓的“好”值。

虽然我不认为您的问题有令人满意的答案,但我认为可能有替代方法可以解决您的问题。考虑使用Parallel.ForEach

        Parallel.ForEach(tasks, (task, state) =>
        {
            if (task.Result != null)
                state.Stop();
        });

state.Stop()在发现非空结果时将停止执行并行循环。

除了具有发现“好”值时停止执行的能力外,它在很多(但不是全部)情况下的性能也会更好。

答案 3 :(得分:0)

使用Task.WhenAny返回完成的任务。检查它是否为空。如果是这样,请将其从列表中删除,然后再次调用Task.WhenAny。

如果情况不错,请取消列表中的所有任务(它们都应带有CancellationTokenSource.Token

编辑:

所有任务都应使用相同的CancellationTokenSource.Token。然后,您只需取消一次。 这里有一些代码需要澄清:

private async void button1_Click(object sender, EventArgs e)
{
    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

    List<Task<string>> tasks = new List<Task<string>>();
    tasks.Add(Task.Run<string>(() => // run your tasks
       {
           while (true)
           {
               if (cancellationTokenSource.Token.IsCancellationRequested)
               {
                   return null;
               }
               return "Result";  //string or null
           }
       }));
    while (tasks.Count > 0)
    {
        Task<string> resultTask = await Task.WhenAny(tasks);
        string result = await resultTask;
        if (result == null)
        {
            tasks.Remove(resultTask);
        }
        else
        {
            // success
            cancellationTokenSource.Cancel(); // will cancel all tasks
        }
    }
}