等待单个任务更清楚地从List <Task >中失败,可能使用LINQ?

在我的应用程序中,我有一个List<Task> ,我在Task.Wait[..]上确定它们是否成功完成( Result = true )。 虽然如果在我等待一个Task完成并返回一个假值,我想取消我正在等待的所有其他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实现,您可以创建类似于接收filter的扩展重载。

此方法返回一个Task ,该Task将在任何提供的任务完成且结果通过filter时完成。

像这样的东西:

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

Jon Skeet , Stephen Toub和我自己都对“完成顺序”方法有所不同。

然而,我发现,如果他们把注意力集中在一点点,人们通常不需要这种复杂性。

在这种情况下,您有一组任务,并希望在其中一个任务返回false立即取消它们。 而不是从控制器的角度考虑它(“ 调用代码如何做到这一点”),从任务角度考虑它(“ 每个任务如何做到这一点”)。

如果你引入了一个更高级别的异步操作“做工作,然后在必要时取消”,你会发现你的调用代码很好地清理:

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