Coder Perfect

In linq select, async await is used.

Problem

I need to change the following code in an existing program:

var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();

But this strikes me as odd, especially the use of async and the wait for the select. According to Stephen Cleary’s response, I should be able to drop those.

Then there’s the second Select, which chooses the outcome. Isn’t this implying that the task isn’t async at all and is done synchronously (so much effort for nothing), or that the task will be done asynchronously and the rest of the query will be executed when it’s completed?

Should I write the above code as follows, as suggested by Stephen Cleary in another answer:

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();

Is everything exactly the same as this?

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();

I’d like to update the first code sample while working on this project, however I’m not keen on changing (apparently working) async code. Maybe I’m overthinking things and all three code samples perform the same thing?

This is how ProcessEventsAsync looks:

async Task<InputResult> ProcessEventAsync(InputEvent ev) {...}

Asked by Alexander Derck

Solution #1

var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();

The Select command is correct. Both of these lines are nearly identical:

events.Select(async ev => await ProcessEventAsync(ev))
events.Select(ev => ProcessEventAsync(ev))

(There’s a minor difference regarding how a synchronous exception would be thrown from ProcessEventAsync, but in the context of this code it doesn’t matter at all.)

This indicates that the query is stalling. As a result, it isn’t truly asynchronous.

Breaking it down:

var inputs = events.Select(async ev => await ProcessEventAsync(ev))

For each event, an asynchronous action will be started first. Then there’s this:

                   .Select(t => t.Result)

will wait for each operation to complete one at a time (for example, it will wait for the first event’s operation, then the second, and so on).

This is the section of the code that I dislike because it blocks and wraps any exceptions in AggregateException.

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();

Yes, the two examples are interchangeable. All asynchronous operations (events) are started by them both. Select(…)), then asynchronously wait for all the operations to complete in any order (await Task. WhenAll(…)), then proceed with the rest of the work (Where…).

These two instances are not the same as the original code. The original function is blocking, and any exceptions will be wrapped in an AggregateException.

Answered by Stephen Cleary

Solution #2

Existing code is functional, however it is preventing the thread from progressing.

.Select(async ev => await ProcessEventAsync(ev))

For each event, it produces a new Task, but

.Select(t => t.Result)

The thread is blocked while it waits for each new task to complete.

Your code, on the other hand, achieves the same objective while remaining asynchronous.

On your first code, there’s only one thing I’d like to say. This phrase

var tasks = await Task.WhenAll(events...

Because the variable should be named in singular, it will produce a single TaskTResult[]>.

Finally, your last code accomplishes the same goal but in a more concise manner.

Task is a good example. Task / Wait. WhenAll

Answered by tede24

Solution #3

I used the following code:

public static async Task<IEnumerable<TResult>> SelectAsync<TSource,TResult>(
    this IEnumerable<TSource> source, Func<TSource, Task<TResult>> method)
{
  return await Task.WhenAll(source.Select(async s => await method(s)));
}

like this:

var result = await sourceEnumerable.SelectAsync(async s=>await someFunction(s,other params));

Edit:

Some have brought up the issue of concurrency, such as when accessing a database and being unable to perform two operations at the same time. So here’s a more advanced version that also allows for a set level of concurrency:

public static async Task<IEnumerable<TResult>> SelectAsync<TSource, TResult>(
    this IEnumerable<TSource> source, Func<TSource, Task<TResult>> method,
    int concurrency = int.MaxValue)
{
    var semaphore = new SemaphoreSlim(concurrency);
    try
    {
        return await Task.WhenAll(source.Select(async s =>
        {
            try
            {
                await semaphore.WaitAsync();
                return await method(s);
            }
            finally
            {
                semaphore.Release();
            }
        }));
    } finally
    {
        semaphore.Dispose();
    }
}

It behaves precisely like the simpler version above without an argument. It will run all jobs in the following order if the argument is set to 1:

var result = await sourceEnumerable.SelectAsync(async s=>await someFunction(s,other params),1);

Note that executing the jobs in order does not imply that the execution will stop in the event of a mistake!

All tasks will be executed, just like with a bigger concurrency value or no parameter supplied, and if any of them fail, the resulting AggregateException will contain the thrown exceptions.

If you want to run jobs in order and fail at the first one, try xhafan’s approach (https://stackoverflow.com/a/64363463/379279).

Answered by Siderite Zackwehdex

Solution #4

As an extension method, I prefer:

public static async Task<IEnumerable<T>> WhenAll<T>(this IEnumerable<Task<T>> tasks)
{
    return await Task.WhenAll(tasks);
}

As a result, it can be used with method chaining:

var inputs = await events
  .Select(async ev => await ProcessEventAsync(ev))
  .WhenAll()

Answered by Daryl

Solution #5

It appears to be quite ugly with the existing ways provided in Linq:

var tasks = items.Select(
    async item => new
    {
        Item = item,
        IsValid = await IsValid(item)
    });
var tuples = await Task.WhenAll(tasks);
var validItems = tuples
    .Where(p => p.IsValid)
    .Select(p => p.Item)
    .ToList();

Hopefully, future versions of.NET will have more elegant tooling for dealing with collections of tasks and collections of tasks.

Answered by Vitaliy Ulantikov

Post is based on https://stackoverflow.com/questions/35011656/async-await-in-linq-select