I have the following code, converting items between the types R
and L
using an async method:
class MyClass<R,L> {
public async Task<bool> MyMethodAsync(List<R> remoteItems) {
...
List<L> mappedItems = new List<L>();
foreach (var remoteItem in remoteItems )
{
mappedItems.Add(await MapToLocalObject(remoteItem));
}
//Do stuff with mapped items
...
}
private async Task<L> MapToLocalObject(R remoteObject);
}
Is this possible to write using an IEnumerable.Select call (or similar) to reduce lines of code? I tried this:
class MyClass<R,L> {
public async Task<bool> MyMethodAsync(List<R> remoteItems) {
...
List<L> mappedItems = remoteItems.Select<R, L>(async r => await MapToLocalObject(r)).ToList<L>();
//Do stuff with mapped items
...
}
}
But i get error:
"Cannot convert async lambda expression to delegate type
'System.Func<R,int,L>'
. An async lambda expression may returnvoid
,Task
orTask<T>
, none of which are convertible to'System.Func<R,int,L>'
."
I believe i am missing something about the async/await keywords, but i cannot figure out what. Does any body know how i can modify my code to make it work?
You can work this out by considering the types in play. For example, MapToLocalObject
- when viewed as an asynchronous function - does map from R
to L
. But if you view it as a synchronous function, it maps from R
to Task<L>
.
Task
is a "future", so Task<L>
can be thought of as a type that will produce an L
at some point in the future.
So you can easily convert from a sequence of R
to a sequence of Task<L>
:
IEnumerable<Task<L>> mappingTasks = remoteItems.Select(remoteItem => MapToLocalObject(remoteItem));
Note that there is an important semantic difference between this and your original code. Your original code waits for each object to be mapped before proceeding to the next object; this code will start all mappings concurrently.
Your result is a sequence of tasks - a sequence of future L
results. To work with sequences of tasks, there are a few common operations. Task.WhenAll
and Task.WhenAny
are built-in operations for the most common requirements. If you want to wait until all mappings have completed, you can do:
L[] mappedItems = await Task.WhenAll(mappingTasks);
If you prefer to handle each item as it completes, you can use OrderByCompletion
from my AsyncEx library:
Task<L>[] orderedMappingTasks = mappingTasks.OrderByCompletion();
foreach (var task in orderedMappingTasks)
{
var mappedItem = await task;
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With