EDIT
I've changed the title of the question to reflect the issue I had but also an answer on how to achieve this easily.
I am trying to make the 2nd method to return Task<TResult>
instead of Task
as in 1st method but I am getting a cascade of errors as a consequence of trying to fix it.
- I added
return
beforeawait body(partition.Current);
- In turn it asks me to add a return statement below so I added
return null
below - But now the select statement complains that it cannot infer the type argument from the query
- I change
Task.Run
toTask.Run<TResult>
but without success.
How can I fix it ?
The first method comes from http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx, the second method is the overload that I'm trying to create.
public static class Extensions
{
public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
return Task.WhenAll(
from partition in Partitioner.Create(source).GetPartitions(dop)
select Task.Run(async delegate
{
using (partition)
while (partition.MoveNext())
await body(partition.Current);
}));
}
public static Task ForEachAsync<T, TResult>(this IEnumerable<T> source, int dop, Func<T, Task<TResult>> body)
{
return Task.WhenAll(
from partition in Partitioner.Create(source).GetPartitions(dop)
select Task.Run(async delegate
{
using (partition)
while (partition.MoveNext())
await body(partition.Current);
}));
}
}
Usage example :
With this method I'd like to download multiple files in parallel and asynchronously :
private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Artist artist = await GetArtist();
IEnumerable<string> enumerable = artist.Reviews.Select(s => s.ImageUrl);
string[] downloadFile = await DownloadFiles(enumerable);
}
public static async Task<string[]> DownloadFiles(IEnumerable<string> enumerable)
{
if (enumerable == null) throw new ArgumentNullException("enumerable");
await enumerable.ForEachAsync(5, s => DownloadFile(s));
// Incomplete, the above statement is void and can't be returned
}
public static async Task<string> DownloadFile(string address)
{
/* Download a file from specified address,
* return destination file name on success or null on failure */
if (address == null)
{
return null;
}
Uri result;
if (!Uri.TryCreate(address, UriKind.Absolute, out result))
{
Debug.WriteLine(string.Format("Couldn't create URI from specified address: {0}", address));
return null;
}
try
{
using (var client = new WebClient())
{
string fileName = Path.GetTempFileName();
await client.DownloadFileTaskAsync(address, fileName);
Debug.WriteLine(string.Format("Downloaded file saved to: {0} ({1})", fileName, address));
return fileName;
}
}
catch (WebException webException)
{
Debug.WriteLine(string.Format("Couldn't download file from specified address: {0}", webException.Message));
return null;
}
}
I solved it and posting it here, might help anyone having the same issue.
My initial need was a small helper that would quickly download images but also just drop the connection if server does not respond quickly, all this in parallel and asynchronously.
This helper will return you a tuple that contains the remote path, the local path and the exception if one occurred; so quite useful as it's always good to know why faulty downloads have faulted. I think I forgot none of the situations that can occur for a download but you're welcome to comment it.
You can just use
DownloadFileTaskAsync
itself or use theForEachAsync
helper for parallel and asynchronous downloads.Code with an example on how to use it :
I haven't changed the signature of
ForEachAsync
to choose the level of parallelism, I'll let you adjust it as you wish.Output example :
What used to take up to 1 minute now barely takes 10 seconds for the same result :)
And big thanks to the author of these 2 posts :
http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx
http://blogs.msdn.com/b/pfxteam/archive/2012/03/04/10277325.aspx