HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpModerate

Filtering a collection by an async result

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
asyncresultcollectionfiltering

Problem

I'm trying to do something like this:

var loudDogs = dogs.Where(async d => await d.IsYappyAsync);


The "IsYappyAsync" property would return a Task.

Obviously this isn't supported, so instead I've built an extension method called WhereAsync.

public static async Task> WhereAsync(this IEnumerable items, Func> predicate)
{
    var results = new List();
    var tasks = new List>();

    foreach (var item in items)
    {
        var task = predicate.Invoke(item);
        tasks.Add(task);
    }

    var predicateResults = await Task.WhenAll(tasks);

    var counter = 0;
    foreach (var item in items)
    {
        var predicateResult = predicateResults.ElementAt(counter);

        if (predicateResult)
            results.Add(item);

        counter++;
    }

    return results.AsEnumerable();
}


This probably isn't the best approach, but I'm at a loss for something better. Any thoughts?

Solution

There are several ways to achieve what you're after and it depends on whether you want the results drip fed to you as they're available or whether you're happy to have them all in one bang.
The way you've implemented your method gives it all in one bang - which is fine.

A shorter implementation could be

public static async Task> WhereAsync2(this IEnumerable items, Func> predicate)
{
    var itemTaskList = items.Select(item=> new {Item = item, PredTask = predicate.Invoke(item)}).ToList();
    await Task.WhenAll(itemTaskList.Select(x=>x.PredTask));
    return itemTaskList.Where(x=>x.PredTask.Result).Select(x=>x.Item);
}


-
It builds a list of an anonymous type where the type contains the item and the Task.

-
It then waits for all of the tasks to complete.

-
It then goes through that list from (1) and picks out the items that had a true result from the Task.

The other advantage is that you get rid of all of that counter and ElementAt() stuff. Either way still builds a complete List for the items in the given Enumerable...

Aside: You could investigate something like Rx if you wanted the results via an IObservable instead. The result wouldn't even be async Task - it would just be an IObservable.

Code Snippets

public static async Task<IEnumerable<T>> WhereAsync2<T>(this IEnumerable<T> items, Func<T, Task<bool>> predicate)
{
    var itemTaskList = items.Select(item=> new {Item = item, PredTask = predicate.Invoke(item)}).ToList();
    await Task.WhenAll(itemTaskList.Select(x=>x.PredTask));
    return itemTaskList.Where(x=>x.PredTask.Result).Select(x=>x.Item);
}

Context

StackExchange Code Review Q#32160, answer score: 18

Revisions (0)

No revisions yet.