等待异步谓词列表,但是先丢弃

想象一下以下课程:

public class Checker { public async Task Check() { ... } } 

现在,想象一下这个类的实例列表:

 IEnumerable checkers = ... 

现在我想控制每个实例都将返回true

 checkers.All(c => c.Check()); 

现在,这将无法编译,因为Check()返回一个Task而不是bool

所以我的问题是:我怎样才能最好地列举检查器列表? 如果检查器返回false我怎样才能快速枚举枚举? (我认为All( )已经存在的东西)

如果检查器返回false,我怎样才能快速枚举枚举?

这将按完成顺序检查任务的结果。 因此,如果第一个任务#5完成,并返回false,则该方法立即返回false,而不管其他任务如何。 永远不会检查较慢的任务(#1,#2等)。

 public static async Task AllAsync(this IEnumerable> source) { var tasks = source.ToList(); while(tasks.Count != 0) { var finishedTask = await Task.WhenAny(tasks); if(! finishedTask.Result) return false; tasks.Remove(finishedTask); } return true; } 

用法:

 bool result = await checkers.Select(c => c.Check()) .AllAsync(); 

“异步序列”总是会引起一些混乱。 例如,目前尚不清楚您所需的语义是否为:

  1. 同时启动所有检查,并在完成后对其进行评估。
  2. 一次启动一个检查,按顺序评估它们。

还有第三种可能性(同时启动所有检查,并按顺序进行评估),但在这种情况下这将是愚蠢的。

我建议将Rx用于异步序列。 它为您提供了很多选择,而且有点难以学习,但它也会迫使您思考您想要的内容。

以下代码将同时启动所有检查并在完成时对其进行评估:

 IObservable result = checkers.ToObservable() .SelectMany(c => c.Check()).All(b => b); 

它首先将检查器序列转换为可观察的,调用它们全部检查,并检查它们是否都是true 。 第一个使用false值完成的Check将导致result产生false值。

相反,以下代码将一次启动一个检查,按顺序评估它们:

 IObservable result = checkers.Select(c => c.Check().ToObservable()) .Concat().All(b => b); 

它首先将检查器序列转换为可观察序列,然后连接这些序列(一次启动一个序列)。

如果您不希望使用可观察量太多并且不想混淆订阅,您可以直接await它们。 例如,在所有检查器上调用Check并在完成时评估结果:

 bool all = await checkers.ToObservable().SelectMany(c => c.Check()).All(b => b); 

All都不是以async为基础构建的(就像所有LINQ ),因此您需要自己实现:

 async Task CheckAll() { foreach(var checker in checkers) { if (!await checker.Check()) { return false; } } return true; } 

您可以使用通用扩展方法使其更具可重用性:

 public static async Task AllAsync(this IEnumerable source, Func> predicate) { foreach (var item in source) { if (!await predicate(item)) { return false; } } return true; } 

并像这样使用它:

 var result = await checkers.AllAsync(c => c.Check()); 

你可以做到

 checkers.All(c => c.Check().Result); 

但这会同步运行任务,这可能会非常慢,具体取决于Check()的实现。

这是一个function齐全的测试程序,遵循dcastro的步骤:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AsyncCheckerTest { public class Checker { public int Seconds { get; private set; } public Checker(int seconds) { Seconds = seconds; } public async Task CheckAsync() { await Task.Delay(Seconds * 1000); return Seconds != 3; } } class Program { static void Main(string[] args) { var task = RunAsync(); task.Wait(); Console.WriteLine("Overall result: " + task.Result); Console.ReadLine(); } public static async Task RunAsync() { var checkers = new List(); checkers .AddRange(Enumerable.Range(1, 5) .Select(i => new Checker(i))); return await checkers .Select(c => c.CheckAsync()) .AllAsync(); } } public static class ExtensionMethods { public static async Task AllAsync(this IEnumerable> source) { var tasks = source.ToList(); while (tasks.Count != 0) { Task finishedTask = await Task.WhenAny(tasks); bool checkResult = finishedTask.Result; if (!checkResult) { Console.WriteLine("Completed at " + DateTimeOffset.Now + "...false"); return false; } Console.WriteLine("Working... " + DateTimeOffset.Now); tasks.Remove(finishedTask); } return true; } } } 

这是示例输出:

 Working... 6/27/2014 1:47:35 AM -05:00 Working... 6/27/2014 1:47:36 AM -05:00 Completed at 6/27/2014 1:47:37 AM -05:00...false Overall result: False 

请注意,当达到退出条件时,整个eval结束,而不等待其余的完成。

作为一种更开箱即用的替代方案,这似乎并行运行任务并在第一次失败后不久返回:

 var allResult = checkers .Select(c => Task.Factory.StartNew(() => c.Check().Result)) .AsParallel() .All(t => t.Result); 

我对TPL和PLINQ不太热,所以请随时告诉我这有什么问题。