I'm trying to use the LINQ IEnumerable.Aggregate function to create a string consisting of files retrieved through async calls. Not a hundred percent sure that it's possible, and I'm also aware that there are other solutions, but I'd like to give it a try.
For now my code looks like this:
private static async Task<string> GetFiles(IEnumerable<string> filePaths)
{
return filePaths.Aggregate(async (current, path) => current + await GetFile(path));
}
But the "async" inside the method call is error marked saying "the return of an async method must be void, Task, or Task". I get that error in general, but I'm not sure how to arrange this specific case to avoid it. Any ideas?
UPDATE:
Just to clarify, the GetFile() method is indeed asynchronous and returns Task<string>
:
private static async Task<string> GetFile(string filePath) { ... }
No need to get into the specific code, but for those interested it uses HttpClient.GetAsync(filePath)
and the returns its response.Content.ReadAsStringAsync().Result
.
Aggregate
method won't work asynchronously. It doesn't support Task based delegates. You need to create a result sequence yourself by awaiting it in prior to call Aggregate
method.
Something like this should work:
private static async Task<string> GetFiles(IEnumerable<string> filePaths)
{
var files = filePaths
.Select(p => GetFile(p))
.ToArray();
var results = await Task.WhenAll(files);
return results
.Aggregate((current, path) => current + path);
}
As @Sriram said, LINQ and async-await
don't work that well together because there's no built-in support for async Task delegates.
What you can do is create an async overload of aggregate yourself:
public static class AsynchronousEnumerable
{
public static async Task<TSource> AggregateAsync<TSource>
(this IEnumerable<TSource> source,
Func<TSource, TSource, Task<TSource>> func)
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
if (!e.MoveNext())
{
throw new InvalidOperationException("Sequence contains no elements");
}
TSource result = e.Current;
while (e.MoveNext()) result = await func(result, e.Current);
return result;
}
}
}
And now you can do the following:
private static Task<string> GetFiles(IEnumerable<string> filePaths)
{
return filePaths.AggregateAsync(async (current, path) => current +
await GetFile(path));
}
If you want to use async
inside Aggregate
, you must realize that anything asynchronous always returns a Task. Taking this into account, it becomes obvious that the result of your Aggregate
call should therefore also be a Task.
For example, calculating the sum of a collection of numbers that are returned asynchronously:
private static async Task<int> GetSumAsync(IEnumerable<Task<int>> numbers) {
return await numbers
.Aggregate(Task.FromResult(0), async (sumSoFar, nextNumber) => (await sumSoFar) + (await nextNumber));
}
I am a little confused as to what you're exactly hoping to do with your GetFiles method. You do realize that Aggregate reduces a collection to just one thing, right? (The 'sum' function is a good example)
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