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
Loading Image from Code Using Relative Path in Windows Forms
Replace Part of a JSON with Other (Using a String Token)
How to Clear Event Subscriptions in C#
How to Unit Test with Ilogger in ASP.NET Core
Register Background Task in Silverlight 8.1 App
Methodinfo.Invoke with Out Parameter
Retrieve the Respective Coordinates of All Words on the Page with Itextsharp
How to Use a Dataadapter with Stored Procedure and Parameter
Print List of Objects to Console
Ilookup<Tkey, Tval> VS. Igrouping<Tkey, Tval>
How to Add Event Handler for Dynamically Created Controls at Runtime
Rounding Up to 2 Decimal Places in C#
How to Ensure a Timestamp Is Always Unique
Access Form Component from Another Class
Is Async Httpclient from .Net 4.5 a Bad Choice for Intensive Load Applications