0

I'm currently working with the Async CTP and need to convert this code into code where I can use Task.WhenAll().

What I did until now was using the UserState object and put my identifier (AID) into it and then use it in the completed event.

However the wc.DownloadFileTaskAsync methode doesn't have an overload with UserState. What can I do?

for (int i = 0; i < SortedRecommendations.Count; i++)
{
    string tempfilepath = filepath + SortedRecommendations[i].Aid + ".jpg";

    if (File.Exists(tempfilepath))
        continue;

    WebClient wc = new WebClient();
    wc.DownloadFileCompleted += (s, e) =>
        {
            var q = SortedRecommendations.Where(x => x.Aid == (int)e.UserState);
            if (q.Count() > 0)
                q.First().Image = tempfilepath;
        };
    wc.DownloadFileAsync(new Uri(SortedRecommendations[i].Image.Replace("t.jpg", ".jpg")), tempfilepath, SortedRecommendations[i].Aid);
}

This is basically with what I came up with, however I'm getting a out ouf bounds exception at y.Aid == SortedRecommendations[i].Aid because i is now obvioulsy something else then it was when the download started. Only other possibility I see is using something like TaskEx.Run( () => { // download data synchronously }; but I don't like this approach.

for (int i = 0; i < SortedRecommendations.Count; i++)
{
    string tempfilepath = filepath + SortedRecommendations[i].Aid + ".jpg";

    if (File.Exists(tempfilepath))
        continue;

    WebClient wc = new WebClient();
    wc.DownloadFileCompleted += (s, e) =>
    {
        var q = SortedRecommendations.Where(x => x.Aid == SortedRecommendations[i].Aid);
        if (q.Count() > 0)
            q.First().Image = tempfilepath;

    };
    tasks.Add(wc.DownloadFileTaskAsync(new Uri(SortedRecommendations[i].Image.Replace("t.jpg", ".jpg")), tempfilepath));
}

await TaskEx.WhenAll(tasks);
//Everything finished
martinyyyy
  • 1,652
  • 3
  • 21
  • 44
  • If you can, you should use VS2012 RC instead of the CTP. It contains many improvements and bug fixes. – svick Jul 20 '12 at 13:10
  • Also, are you sure you want to start downloading all of the files at the same time? – svick Jul 20 '12 at 13:13
  • yes I want to download all the files at the same time (+ afaik the ctp uses the thread pool and chooses a appropiate number of parallel threads.). And I can't use .NET 4.5 because I rely on Blend. And Blend, even the one you get with VS12, doesn't work properly with .NET 4.5. (Found this out the hard way...) – martinyyyy Jul 20 '12 at 13:19
  • It does use the `ThreadPool` (under normal circumstances), but that can choose appropriate number of threads only for short CPU-bound operations. It doesn't work for async IO operations at all, because the async parts don't use up any threads. Because of that, all of your files will start downloading at about the same time. – svick Jul 20 '12 at 13:27

1 Answers1

0

First, I think you shouldn't base your logic on ids (unless you really have to). You should use references to the objects in the SortedRecommendations collection.

Now, if you wanted to download only one file at a time, you could simply use await:

for (int i = 0; i < SortedRecommendations.Count; i++)
{
    string tempfilepath = filepath + SortedRecommendations[i].Aid + ".jpg";

    if (File.Exists(tempfilepath))
        continue;

    WebClient wc = new WebClient();
    var recommendation = SortedRecommendations[i];
    await wc.DownloadFileTaskAsync(new Uri(recommendation.Image.Replace("t.jpg", ".jpg")), tempfilepath);
    recommendation.Image = tempfilepath;
}

But, if you wanted to start all of the downloads at the same time, like your DownloadFileAsync() code does, you could use ContinueWith() instead. And you don't need user state, that's what closures are for:

for (int i = 0; i < SortedRecommendations.Count; i++)
{
    string tempfilepath = filepath + SortedRecommendations[i].Aid + ".jpg";

    if (File.Exists(tempfilepath))
        continue;

    WebClient wc = new WebClient();
    var recommendation = SortedRecommendations[i];
    var downloadTask = wc.DownloadFileTaskAsync(new Uri(recommendation.Image.Replace("t.jpg", ".jpg")), tempfilepath);
    var continuation = downloadTask.ContinueWith(t => recommendation.Image = tempfilepath);
    tasks.Add(continuation);
}

await Task.WhenAll(tasks);

The best solution would probably be to download a limited number of files at once, not one or all of them. Doing that is more complicated and one solution would be to use ActionBlock from TPL Dataflow with MaxDegreeOfParallelism set.

svick
  • 236,525
  • 50
  • 385
  • 514