Task.WaitAny () - Проверка результатов - PullRequest
0 голосов
/ 30 августа 2018

У меня есть серия задач в массиве. Если Задача «Хорошая», она возвращает строку. Если это «Плохо»: возвращается ноль.

Я хочу иметь возможность запускать все Задачи параллельно, и как только вернется первое «Хорошее», отмените остальные и получите «Хороший» результат.

Я делаю это сейчас, но проблема в том, что все задачи должны быть запущены, затем я перебираю их в поисках первого хорошего результата.

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

Ответы [ 4 ]

0 голосов
/ 31 августа 2018

Я искал Task.WhenAny(), который сработает при первом «выполненном» задании. К сожалению, выполненное задание в этом смысле - это что угодно ... даже исключение считается «выполненным». Насколько я могу судить, нет другого способа проверить, что вы называете «хорошим» значением.

Хотя я не верю, что на ваш вопрос есть удовлетворительный ответ, я думаю, что может быть альтернативное решение вашей проблемы. Попробуйте использовать Parallel.ForEach.

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

state.Stop() прекратит выполнение цикла Parallel, когда обнаружит ненулевой результат.

Помимо возможности прекратить выполнение, когда он находит «хорошее» значение, он будет работать лучше во многих (но не во всех) сценариях.

0 голосов
/ 30 августа 2018

Вы можете достичь желаемых результатов, используя следующий пример.

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

}  

РЕДАКТИРОВАТЬ : Если вы хотите завершить все задачи, вы можете использовать CancellationToken.

Подробнее читайте в документах .

0 голосов
/ 31 августа 2018

Я хочу иметь возможность запускать все Задачи параллельно, и как только вернется первое «Хорошее», отмените остальные и получите «Хорошее».

Это недоразумение, поскольку Отмена в TPL является кооперативной , поэтому после запуска Задачи отменить ее невозможно. CancellationToken может работать до запуска Задачи или позже, чтобы вызвать исключение, если запрашивается Отмена, которая предназначена для инициирования и выполнения необходимых действий, таких как выбрасывание пользовательского исключения из логики

Проверьте следующий запрос , в нем есть много интересных ответов, но ни один из них Отмена. Ниже также возможен вариант:

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> для необходимой логической обработки

0 голосов
/ 30 августа 2018

Использование Task.WhenAny Возвращает завершенное задание. Проверьте, если это нуль. Если это так, удалите его из списка и вызовите Task.WhenAny Again.

Если это хорошо, отмените все задачи в списке (все они должны иметь CancellationTokenSource.Token.

Edit:

Все задачи должны использовать один и тот же 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
        }
    }
}
...