Apr 5, 2013

Downloading multiple files on Windows Phone 8

What I think is missing from the internet is a posts with examples of solving simple tasks that usually takes longer then they should. So I decided to share some.

What I wanted to do is to download a number of images from the internet, combine them and use it as a background for my app. The simple way to do that is with the help of build in BitmapImage class:

BitmapImage image = new BitmapImage(new Uri("http://test.jpg"));
image.ImageOpened += (sender, args) => {
    Process(); // here we have image downloaded
};

Each time I need to create new Uri I ask myself why there is no constructor that accepts string…

So what went wrong with the code above? The problem is that I needed all images content at the same time to combine them and put results to cache or something. First solution I came up with was using Interlocked.Increment() method. It solved problem in some way. But code became complicated and I didn’t like that. So here a better way to do that. In order to have determined point in time where all images are downloaded I created my file downloader helper:

public class ImageDownloader
{
    public static async Task<MemoryStream> Download(string url)
    {
        var request = (HttpWebRequest)HttpWebRequest.Create(url);

        var response = await Task<WebResponse>.Factory.FromAsync(request.BeginGetResponse, request.EndGetResponse, null);
        var result = new MemoryStream();
        using (var responseStream = response.GetResponseStream())
        {
            responseStream.CopyTo(result);
        }
        result.Position = 0;

        return result;
    }
}

Maybe returning Stream is not the best way to do it, but I think its ok for this example post. So code is simple and self-explanatory. It just downloads any file by provided URL.

Having that helper, we can compose list of tasks and execute processing just after receiving all images:

var imageUrls = new[] {
    string.Format("http://placekitten.com/{0}/{1}", imageWidth, imageHeight), 
    string.Format("http://placekitten.com/g/{0}/{1}", imageWidth, imageHeight)
};

var downloadTasks = new List<Task<MemoryStream>>();
foreach (var image in imageUrls)
{
    downloadTasks.Add(ImageDownloader.Download(image));
}

Task.Factory.ContinueWhenAll(downloadTasks.ToArray(), tasks => {
    WriteableBitmap image1 = new WriteableBitmap(imageWidth, imageHeight);
    image1.LoadJpeg(tasks[0].Result);
    WriteableBitmap image2 = new WriteableBitmap(imageWidth, imageHeight);
    image2.LoadJpeg(tasks[1].Result);

    Image1.Source = image1;
    Image2.Source = image2;
}, 
    CancellationToken.None, 
    TaskContinuationOptions.None, 
    TaskScheduler.FromCurrentSynchronizationContext() // this will make sure we are changing UI in the thread that is allowed to do that.
);

This code uses Task Parallel Library’s method ContinueWhenAll. It allows you to execute task after all other tasks are finished.

So that all what is needed. It is really simple, but took me a while to find the right classes and syntax to solve the problem.

You can download a working example here.

No comments:

Post a Comment