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
.
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;
}
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);
LINQ Select analogue for async method
Looks like there is no other solution currently (until C# 8.0) except enumerating it manually. I made an extension method for that (returns a List instead of Array as it's simpler):
public static async Task<List<T>> ToListAsync<T>(this IEnumerable<Task<T>> source)
{
var result = new List<T>();
foreach (var item in source)
{
result.Add(await item);
}
return result;
}
Related Topics
Convert PDF to Image Without Using Ghostscript Dll
How to Make My Windows Form App Snap to Screen Edges
How to Clear Cookies Using ASP.NET MVC 3 and C#
Create/Use User-Defined Functions in System.Data.Sqlite
Embedding a File Explorer Instance in a Windows Forms Application Form
Convert Transparent Png to Jpg with Non-Black Background Color
How to Split a String into Multiple Values
How to Add an Ampersand for a Value in a ASP.NET/C# App Config File Value
Ilookup<Tkey, Tval> VS. Igrouping<Tkey, Tval>
Linq to Entities Does Not Recognize the Method 'System.Web.Mvc.Fileresult'
Disable Application Insights in Debug
How to Convert a Datetime to the Number of Seconds Since 1970
Why How to Not Edit a Method That Contains an Anonymous Method in the Debugger
How to Spawn Threads on Different CPU Cores