13
\$\begingroup\$

I'm trying to do something like this:

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

The "IsYappyAsync" property would return a Task<bool>.

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

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

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

    var predicateResults = await Task.WhenAll<bool>(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?

\$\endgroup\$
18
\$\begingroup\$

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<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);
}
  1. It builds a list of an anonymous type where the type contains the item and the Task.

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

  3. 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<T> instead. The result wouldn't even be async Task - it would just be an IObservable.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ This is nice, bookmarked for later usage, thank you \$\endgroup\$ – Daniel Dawes Oct 3 '13 at 10:57
  • 1
    \$\begingroup\$ I don't like this approach. Using .Result is most likely never the correct answer. \$\endgroup\$ – El Mac Sep 6 '19 at 14:05
  • \$\begingroup\$ Thabks for the comment all these years later 😉The tasks are known to be complete, so it's safe in this context. I appreciate that's a distinction often missed by many, but it's OK in this context. (eventually, somewhere in the code, even if it's not your code but something invoked by the async/await state machine, something is calling .Result to actually get the result) \$\endgroup\$ – Ian Yates Sep 7 '19 at 20:34
  • \$\begingroup\$ Agree with @ElMac. Using .Result is usually a code smell, and in this case it certainly is. It is definitely not "safe" or "OK in this context". You have made the code synchronous as .Result blocks. You are also ignoring the return of await WhenAll which is also a code smell. The result of await WhenAll is exactly what you should be performing the Where on. \$\endgroup\$ – Neo Nov 15 '20 at 20:04
  • \$\begingroup\$ @neo, Thanks for the comment. .Result is synchronous, and blocks if there's no result yet. We know that every single one of these Tasks has a result. That's what awaiting Task.WhenAll is doing in this code. It is "OK in this context" - sorry you disagree. As it turns out, I'd probably write this differently all these years later (and would've at least deliberately noted via comment that .Result was intentional), but it's not broken Using Tasks that return an anonymous type, with a bool member, would allow for the filtering you suggest and is probably nicer overall. \$\endgroup\$ – Ian Yates Nov 16 '20 at 5:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.