Ожидание выполнения одной задачи из списка <Задача > более чисто, возможно, с помощью LINQ?

В моем приложении у меня есть List<Task> , для которого я Task.Wait[..] чтобы определить, успешно ли они выполнены ( Result = true ). Хотя, если во время моего ожидания Task завершается и возвращает значение false, я хочу отменить все остальные Task я ожидаю, и сделать что-то на основе этого.

Я создал два «уродливых» метода для этого

 // Create a CancellationToken and List<Task> to work with CancellationToken myCToken = new CancellationToken(); List<Task> myTaskList = new List<Task>(); //-- Method 1 -- // Wait for one of the Tasks to complete and get its result Boolean finishedTaskResult = myTaskList[Task.WaitAny(myTaskList.ToArray(), myCToken)].Result; // Continue waiting for Tasks to complete until there are none left or one returns false while (myTaskList.Count > 0 && finishedTaskResult) { // Wait for the next Task to complete finishedTaskResult = myTaskList[Task.WaitAny(myTaskList.ToArray(), myCToken)].Result; if (!finishedTaskResult) break; } // Act on finishTaskResult here // -- Method 2 -- // Create a label to WaitForOneCompletion: int completedTaskIndex = Task.WaitAny(myTaskList.ToArray(), myCToken); if (myTaskList[completedTaskIndex].Result) { myTaskList.RemoveAt(completedTaskIndex); goto WaitForOneCompletion; } else ;// One task has failed to completed, handle appropriately 

Мне было интересно, есть ли более чистый способ сделать это, возможно, с LINQ?

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

 public static IEnumerable> Order(this IEnumerable> tasks) { var taskList = tasks.ToList(); var taskSources = new BlockingCollection>(); var taskSourceList = new List>(taskList.Count); foreach (var task in taskList) { var newSource = new TaskCompletionSource(); taskSources.Add(newSource); taskSourceList.Add(newSource); task.ContinueWith(t => { var source = taskSources.Take(); if (t.IsCanceled) source.TrySetCanceled(); else if (t.IsFaulted) source.TrySetException(t.Exception.InnerExceptions); else if (t.IsCompleted) source.TrySetResult(t.Result); }, CancellationToken.None, TaskContinuationOptions.PreferFairness, TaskScheduler.Default); } return taskSourceList.Select(tcs => tcs.Task); } 

Теперь, когда у вас есть возможность заказать задания на основе их завершения, вы можете написать код в основном так же, как ваши требования диктуют:

 foreach(var task in myTaskList.Order()) if(!await task) cancellationTokenSource.Cancel(); 

Используя реализацию Task.WhenAny , вы можете создать как перегрузку расширения, которая также получает фильтр.

Этот метод возвращает Task которое будет завершено, когда какая-либо из поставленных задач будет завершена, и результат пройдет через фильтр.

Что-то вроде этого:

 static class TasksExtensions { public static Task> WhenAny(this IList> tasks, Func filter) { CompleteOnInvokePromiseFilter action = new CompleteOnInvokePromiseFilter(filter); bool flag = false; for (int i = 0; i < tasks.Count; i++) { Task completingTask = tasks[i]; if (!flag) { if (action.IsCompleted) flag = true; else if (completingTask.IsCompleted) { action.Invoke(completingTask); flag = true; } else completingTask.ContinueWith(t => { action.Invoke(t); }); } } return action.Task; } } class CompleteOnInvokePromiseFilter { private int firstTaskAlreadyCompleted; private TaskCompletionSource> source; private Func filter; public CompleteOnInvokePromiseFilter(Func filter) { this.filter = filter; source = new TaskCompletionSource>(); } public void Invoke(Task completingTask) { if (completingTask.Status == TaskStatus.RanToCompletion && filter(completingTask.Result) && Interlocked.CompareExchange(ref firstTaskAlreadyCompleted, 1, 0) == 0) { source.TrySetResult(completingTask); } } public Task> Task { get { return source.Task; } } public bool IsCompleted { get { return source.Task.IsCompleted; } } } 

Вы можете использовать этот метод расширения следующим образом:

 List> tasks = new List>(); ...Initialize Tasks... var task = await tasks.WhenAny(x => x % 2 == 0); //In your case would be something like tasks.WhenAny(b => b); 

У Джона Скита , Стивена Туба и у меня все есть варианты подхода «порядок по завершению».

Однако я считаю, что обычно людям не нужна такая сложность, если они фокусируют свое внимание немного по-другому.

В этом случае у вас есть набор задач и вы хотите их отменить, как только один из них вернет false . Вместо того, чтобы думать об этом с точки зрения controllerа («как может код вызова сделать это»), подумайте об этом с точки зрения задачи («как каждая задача может это сделать»).

Если вы вводите асинхронную операцию более высокого уровня, выполните «выполнить работу и затем отмените при необходимости», вы обнаружите, что код вызова очищается красиво:

 public async Task DoWorkAndCancel(Func> work, CancellationTokenSource cts) { if (!await work(cts.Token)) cts.Cancel(); } List>> allWork = ...; var cts = new CancellationTokenSource(); var tasks = allWork.Select(x => DoWorkAndCancel(x, cts)); await Task.WhenAll(tasks);