等待异步谓词列表,但是先丢弃
想象一下以下课程:
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();
“异步序列”总是会引起一些混乱。 例如,目前尚不清楚您所需的语义是否为:
- 同时启动所有检查,并在完成后对其进行评估。
- 一次启动一个检查,按顺序评估它们。
还有第三种可能性(同时启动所有检查,并按顺序进行评估),但在这种情况下这将是愚蠢的。
我建议将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不太热,所以请随时告诉我这有什么问题。