Task.WaitAny() — 检查结果

Task.WaitAny() — Checking for results

我在数组中有一系列任务。如果一个任务是 "Good" 它 return 是一个字符串。如果是 "Bad":它 return 为空。

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

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

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

使用Task.WhenAny它returns完成的任务。检查它是否为空。如果是,将其从列表中删除并再次调用 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
        }
    }
}

您可以使用以下示例实现您想要的结果。

List<Task<string>> tasks = new List<Task<string>>();  

// ***Use ToList to execute the query and start the tasks.   
List<Task<string>> goodBadTasks = tasks.ToList();  

// ***Add a loop to process the tasks one at a time until none remain.  
while (goodBadTasks.Count > 0)  
{  
    // Identify the first task that completes.  
    Task<string> firstFinishedTask = await Task.WhenAny(goodBadTasks);  

    // ***Remove the selected task from the list so that you don't  
    // process it more than once.  
    goodBadTasks.Remove(firstFinishedTask);  

    // Await the completed task.  
    string firstFinishedTaskResult = await firstFinishedTask;  
    if(firstFinishedTaskResult.Equals("good")
         // do something

}  

EDIT :如果您想终止所有任务,您可以使用 CancellationToken。

有关详细信息,请阅读 docs

I want to be able to run all the Tasks in parallel, and once the first one comes back that is "Good", then cancel the others and get the "Good" result.

这是误会,因为Cancellation in TPL is co-operative,所以任务一旦启动,就无法取消。 CancellationToken 可以在任务启动之前或之后抛出异常,如果请求取消,这意味着启动并采取必要的行动,比如从逻辑中抛出自定义异常

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

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

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

我正在研究 Task.WhenAny(),它将在第一个 "completed" 任务上触发。不幸的是,从这个意义上讲,完成的任务基本上是任何东西……即使是例外也被认为是 "completed"。据我所知,没有其他方法可以检查您所谓的 "good" 值。

虽然我认为您的问题没有令人满意的答案,但我认为您的问题可能有其他解决方案。考虑使用 Parallel.ForEach.

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

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

除了能够在找到 "good" 值时停止执行外,它在许多(但不是所有)场景下的表现会更好。