Expect a list of asynchronous predicates, but leave the first false

Imagine the following class:

public class Checker { public async Task<bool> Check() { ... } } 

Now imagine a list of instances of this class:

 IEnumerable<Checker> checkers = ... 

Now I want each instance to return true :

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

Now this does not compile, since Check() returns a Task<bool> not a bool .

So my question is: how can I best list the checkers list? And how can I reduce the enumeration time as soon as the checker returns false ? (something that I assume All( ) already)

+6
source share
6 answers

And how can I reduce the number of listings as soon as the checker returns false?

This will check the result of the task in order of completion. Therefore, if task No. 5 is the first to complete and returns false, the method immediately returns false, regardless of other tasks. Slower tasks (# 1, # 2, etc.) will never be checked.

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

Using:

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

Asynchronous sequences can always cause some confusion. For example, it is not clear if your desired semantics are:

  • Start all checks at the same time and evaluate them as they are completed.
  • Start checks one at a time, evaluating them in order of priority.

There is a third possibility (first run all the checks and evaluate them in sequence), but that would be stupid in this scenario.

I recommend using Rx for asynchronous sequences. It gives you a lot of opportunities, and it's a little hard to learn, but it also makes you think about exactly what you want.

The following code runs all the checks at the same time and evaluates them as they are completed:

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

First, it converts the sequence of checkers into observable, calls all Checks on them and checks if they are all true . The first Check , which ends with false , calls result to create false .

In contrast, the following code runs checks one at a time, evaluating them in sequence:

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

First, it converts the sequence of checkers into a sequence of observables, and then combines these sequences (which start them one at a time).

If you donโ€™t want to use observational data a lot and donโ€™t want to contact subscriptions, you can directly await them. For example, to call Check on all checkers and evaluate the results as they are completed:

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

All not created with async in mind (like all LINQ ), so you need to implement this yourself:

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

You can make it more reusable using the universal extension method:

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

And use it as follows:

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

You could do

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

but this will perform tasks synchronously, which can be very slow depending on the implementation of Check() .

0
source

Here is a fully functional test program following the steps of 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; } } } 

Here's the sample output:

 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 

Note that all eval ended when the exit condition was reached, without waiting for the rest to complete.

0
source

As a simpler alternative, this seems to run tasks in parallel and return shortly after the first crash:

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

I'm not too hot on TPL and PLINQ, so feel free to tell me what's wrong with that.

-1
source

Source: https://habr.com/ru/post/971392/


All Articles