How to Await a List of Tasks Asynchronously Using Linq

Async await in linq select

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

But this seems very weird to me, first of all the use of async and await in the select. According to this answer by Stephen Cleary I should be able to drop those.

The call to Select is valid. These two lines are essentially 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.)

Then the second Select which selects the result. Doesn't this mean the task isn't async at all and is performed synchronously (so much effort for nothing), or will the task be performed asynchronously and when it's done the rest of the query is executed?

It means that the query is blocking. So it is not really asynchronous.

Breaking it down:

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

will first start an asynchronous operation for each event. Then this line:

                   .Select(t => t.Result)

will wait for those operations to complete one at a time (first it waits for the first event's operation, then the next, then the next, etc).

This is the part I don't care for, because it blocks and also would wrap any exceptions in AggregateException.

and is it completely the same like this?

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, those two examples are equivalent. They both start all asynchronous operations (events.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...).

Both of these examples are different from the original code. The original code is blocking and will wrap exceptions in AggregateException.

How to await a list of tasks asynchronously using LINQ?

LINQ doesn't work perfectly with async code, but you can do this:

var tasks = foos.Select(DoSomethingAsync).ToList();
await Task.WhenAll(tasks);

If your tasks all return the same type of value, then you can even do this:

var results = await Task.WhenAll(tasks);

which is quite nice. WhenAll returns an array, so I believe your method can return the results directly:

return await Task.WhenAll(tasks);

Using Async/Await inside nested LINQ-Select without having to use Task.WhenAll

You don't need to change the type of Column you just need to await the results outside of the initializer for your table. That allows you to collect the results in columnTasks, await all of those and then create your new table.

var result = someList.Select(async table =>
{
var columnTasks = table.Select(async column => new Column()
{
Constraints = await GetColumnConstraints()
});
var columns = await Task.WhenAll(columnTasks);
return new Table()
{
Columns = columns
};
});

Notice though that async works its way up the chain so reuslt is now IEnumerable<Task<Table>> and you'll have to await Task.WhenAll that to get your final collection of Table

var tables = await Task.WhenAll(result);

Performance LINQ async await

To simplify, you're asking about the difference between this:

await Task.WhenAll(something.Select(async () => await DoSomethingAsync()).ToArray());

and this:

await Task.WhenAll(something.Select(() => DoSomethingAsync()).ToArray());

for me the correct code is the second because in the first its waiting each end of api?

No, you're misunderstanding the way it works. DoSomethingAsync() will still get called concurrently, and the resulting tasks will still be completed concurrently. The only difference with the added async/await is that the compiler adds some state machine code into the lambda function so that if an exception is thrown, that lambda will appear on your stack trace.

The performance difference coming from that additional state machine code will be negligible compared to any async operations you're using.

There is often value in having the stack trace show where the async method call happened. It's probably not a big deal in this case, since the await Task.WhenAll() is just a line away from the method call, so there's no ambiguity about how DoSomethingAsync was called. But you can imagine scenarios where DoSomethingAsync is called from various different parts of code, and the resulting tasks are awaited in a completely different method: it might be difficult to figure out what path led to the point where an exception was thrown if that line of code isn't included in the stack trace.

Using async/await inside a Select linq query

So at the end of your method, customerTasks contains an IEnumerable<Task> that has not been enumerated. None of the code within the Select even runs.

When creating tasks like this, it's probably safer to materialize your sequence immediately to mitigate the risk of double enumeration (and creating a second batch of tasks by accident). You can do this by calling ToList on your sequence.

So:

var customerTasks = ids.Select(async i =>
{
ICustomerRepo repo = new CustomerRepo();
var id = await repo.getCustomer(i); //consider changing to GetCustomerAsync
Console.WriteLine(id);

}).ToList();

Now... what to do with your list of tasks? You need to wait for them all to complete...

You can do this with Task.WhenAll:

await Task.WhenAll(customerTasks);

You could take this a step further by actually returning a value from your async delegate in the Select statement, so you end up with an IEnumerable<Task<Customer>>.

Then you can use a different overload of Task.WhenAll:

IEnumerable<Task<Customer>> customerTasks = ids.Select(async i =>
{
ICustomerRepo repo = new CustomerRepo();
var c = await repo.getCustomer(i); //consider changing to GetCustomerAsync
return c;

}).ToList();

Customer[] customers = await Task.WhenAll(customerTasks); //look... all the customers

Of course, there are probably more efficient means of getting several customers in one go, but that would be for a different question.

If instead, you'd like to perform your async tasks in sequence then:

var customerTasks = ids.Select(async i =>
{
ICustomerRepo repo = new CustomerRepo();
var id = await repo.getCustomer(i); //consider changing to GetCustomerAsync
Console.WriteLine(id);

});
foreach(var task in customerTasks) //items in sequence will be materialized one-by-one
{
await task;
}

How to return Task list using LINQ or lambda in WinForm

The key is to separate what happens on the database and what happens in memory.

Before you start, though, I'd make a nice helper function that takes out some of the boilerplate code.

private async Task<List<T>> GetDtosAsync<T>(Func<Db, List<T>> getDtos) =>
await Task.Run(() =>
{
using (Db db = new Db())
{
return getDtos(db);
}
});

Note that I'm using Task.Run as Task.Factory.StartNew is outdated and no longer recommended.

Now you can write your method like this:

private async Task<List<OrderDTO>> GetTransactionByDate(DateTime today) =>
await GetDtosAsync(db =>
{
var query =
from u in db.Orders
join p in db.Product on u.ProductId equals p.ProductId
where u.OrderDate == today
select new
{
u.OrderId,
p.ProductName,
u.OrderDate,
u.Price,
};

var totalOrder =
query
.ToList()
.Select(x => new OrderDTO
{
OrderId = x.OrderId,
ProductName = x.ProductName,
Date = x.OrderDate,
Price = x.Price,
})
.ToList();

return totalOrder;
});

It's the query.ToList() that takes the data from the database and brings it in to memory before you construct you final list.

Creating awaitable tasks using LINQ

You shouldn't ever use the Task constructor.

Since you're calling synchronous code (Deserialize), you could use Task.Run:

async Task LoadItems()
{
var tasks = Directory.GetDirectories(somePath)
.Select(dir => Task.Run(() =>
new ItemViewModel(new ItemSerializer().Deserialize(dir))));

foreach (var task in tasks)
{
var result = await task;
DoSomething(result);
}
}

Alternatively, you could use Parallel or Parallel LINQ:

void LoadItems()
{
var vms = Directory.GetDirectories(somePath)
.AsParallel().Select(dir =>
new ItemViewModel(new ItemSerializer().Deserialize(dir)))
.ToList();

foreach (var vm in vms)
{
DoSomething(vm);
}
}

Or, if you make Deserialize a truly async method, then you can make it all asynchronous:

async Task LoadItems()
{
var tasks = Directory.GetDirectories(somePath)
.Select(async dir =>
new ItemViewModel(await new ItemSerializer().DeserializeAsync(dir))));

foreach (var task in tasks)
{
var result = await task;
DoSomething(result);
}
}

Also, I recommend that you do not use fire-and-forget in your constructor. There are better patterns for asynchronous constructors.

Async task method issue on a linq query return in c#

Linq uses the keyword yield return and this denotes that we won't actually do the operation until it's called upon. So your avoid code is trying to await the operation of a lazily invoked method.

per this MSDN

Although it's less code, take care when mixing LINQ with asynchronous code. Because LINQ uses deferred (lazy) execution, async calls won't happen immediately as they do in a foreach() loop unless you force the generated sequence to iterate with a call to .ToList() or .ToArray().

So, lose the wait. You're not actually executing the statement until the results are used



Related Topics



Leave a reply



Submit