C# 等待异步谓词列表,但在第一个false时退出
想象一下下面的类:C# 等待异步谓词列表,但在第一个false时退出,c#,.net,linq,async-await,enumeration,C#,.net,Linq,Async Await,Enumeration,想象一下下面的类: public class Checker { public async Task<bool> Check() { ... } } 现在,这将不会编译,因为Check()返回的是任务而不是bool 所以我的问题是:我怎样才能最好地列举出检查者的名单? 当检查器返回false时,我如何设置枚举的快捷方式? (我认为All()已经做到了这一点)All并没有考虑到async(就像AllLINQ),所以您需要自己实现它: async Task<bool>
public class Checker
{
public async Task<bool> Check() { ... }
}
现在,这将不会编译,因为Check()
返回的是任务
而不是bool
所以我的问题是:我怎样才能最好地列举出检查者的名单?
当检查器返回false
时,我如何设置枚举的快捷方式?
(我认为
All()
已经做到了这一点)All
并没有考虑到async
(就像AllLINQ
),所以您需要自己实现它:
async Task<bool> CheckAll()
{
foreach(var checker in checkers)
{
if (!await checker.Check())
{
return false;
}
}
return true;
}
你可以
checkers.All(c => c.Check().Result);
但这将同步运行任务,这可能非常慢,具体取决于Check()
的实现
当检查器返回false时,我如何设置枚举的快捷方式
这将按完成顺序检查任务的结果。因此,如果task#5是第一个完成的任务,并且返回false,那么不管其他任务如何,该方法都会立即返回false。较慢的任务(1、2等)永远不会被检查
public static async Task<bool> AllAsync(this IEnumerable<Task<bool>> 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;
}
“异步序列”总是会引起一些混乱。例如,不清楚您想要的语义是否为:
IObservable<bool> result = checkers.ToObservable()
.SelectMany(c => c.Check()).All(b => b);
bool all = await checkers.ToObservable().SelectMany(c => c.Check()).All(b => b);
它首先将跳棋序列转换为可观测序列,然后连接这些序列(一次启动一个序列)
如果您不想太多地使用可观察对象,也不想干扰订阅,您可以直接等待它们。例如,调用所有检查器上的Check
,并在其完成时评估结果:
IObservable<bool> result = checkers.ToObservable()
.SelectMany(c => c.Check()).All(b => b);
bool all = await checkers.ToObservable().SelectMany(c => c.Check()).All(b => b);
以下是一个全功能测试程序,按照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<bool> 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<bool> RunAsync()
{
var checkers = new List<Checker>();
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<bool> AllAsync(this IEnumerable<Task<bool>> source)
{
var tasks = source.ToList();
while (tasks.Count != 0)
{
Task<bool> 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;
}
}
}
请注意,当达到退出条件时,整个评估结束,而不等待其余部分完成。作为一种更开箱即用的替代方案,这似乎是并行运行任务,并在第一次失败后不久返回:
var allResult = checkers
.Select(c => Task.Factory.StartNew(() => c.Check().Result))
.AsParallel()
.All(t => t.Result);
我对TPL和PLINQ不太感兴趣,所以请随时告诉我这有什么问题。第二个选项将使委托
异步无效
,这几乎应该是无效的。好吧,它无论如何都不起作用,因为我刚刚测试过它。RX是几个“我知道这对我有好处,但我的大脑会受伤”区域之一。特别是如果您在它上面添加RxUI;-)我不熟悉Rx
,我不确定Rx
如何等待checkers.ToObservable()语句中的结果。选择many(c=>c.Check())。All(b=>b)
。我可以看到人们可以等待一个可观察的,但不确定这一切是如何发生的。其他答案假设只有一个选项。@NedStoyanov:当您等待
一个可观察对象时,代码(异步)等待该可观察对象完成并返回最后一个值。我意识到我可以使用进度来更新控制台,这样在生产中会更好。我试图说明这样一个事实:async/await即使在没有“当前同步上下文”可恢复的情况下也能工作。你为什么先选择,然后AllAsync
,而不是像@I3arnon在他的回答中那样组合它们呢?@Vegar没有特别的原因,但我想,如果你要写一个(AllAsync())
)或其他(AllAsync(Funcok)。尽管我喜欢“流畅的合成”,但我还是喜欢尽可能短的序列。-@Vegar为了最大的灵活性/可读性,您可以始终使用这两种扩展方法:)只需稍微更改签名,并将第一行更改为var tasks=source.Select(谓词).ToList();代码>我怀疑一旦ThreadPool耗尽,您的程序就会阻塞。线程池最好在计划计算密集型工作时使用。如果每次异步检查花费的时间相对较长,那么这将比其他实现花费的时间长得多。@GregC公平,我知道它似乎太简单:)使用async
的目的就是不不必要地阻塞线程,这正是它的作用。而且StartNew()
没有任何意义,如果它只是Select(c=>c.Check())
它也可以工作,并且不会阻塞任何线程。@svick我试过了,它只是无限期地挂起。实际上,这是并行运行线程的。这不是和Task.WhenAll的语义不同吗?我认为dcastro和Cleary的答案更快。@nawfal可能,是的。这取决于是否允许单个检查同时运行,这不一定是真的,但我不记得4年前的上下文。
IObservable<bool> result = checkers.Select(c => c.Check().ToObservable())
.Concat().All(b => b);
bool all = await checkers.ToObservable().SelectMany(c => c.Check()).All(b => b);
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<bool> 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<bool> RunAsync()
{
var checkers = new List<Checker>();
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<bool> AllAsync(this IEnumerable<Task<bool>> source)
{
var tasks = source.ToList();
while (tasks.Count != 0)
{
Task<bool> 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
var allResult = checkers
.Select(c => Task.Factory.StartNew(() => c.Check().Result))
.AsParallel()
.All(t => t.Result);