Marcel B
Marcel B

Reputation: 3674

how to preload images in a background thread?

In my WPF app i need to load some images. I only need to display one image at a time. If i load the image when it's needed, there is a slightly delay. So i thought to myself: "Hey, why not do some preloading in a background thread? Can't be that hard." I have some experience with threads, but not enough to know that this thought was wrong. I started programming and run into some problems. I fixed some of the problems and i probably could fix the other problems too, but that would result in spaghetti code. So, I think starting from the scratch would be the best. What initial planing is needed to build a nice and little preloading thread? Is there a pattern or something like that?

Here's my current setup:

Upvotes: 1

Views: 4756

Answers (4)

James Hay
James Hay

Reputation: 12700

I was looking in to this yesterday and couldn't find much on the subject. There's actually quite a simple solution to the problem. Use WebClient to load the images asynchronously into a stream and then add that stream to a BitmapImage. Bellow is an example of how I implemented the preloading of a list of images. the example uses the Reactive Extensions Library(Rx) but it can easily be implemented in a non Rx way (Rx makes the code a lot more succinct and hides a lot of state).

public IEnumerable<BitmapImage> BitmapImages { get; private set }

private void PreloadImages(IEnumerbale<Uri> uriCollection)
{
    var bitmapImages= new List<BitmapImage>();

    uriCollection.ToObservable()
        .SelectMany(LoadImageAsync)
        .Catch(Observable.Empty<BitmapImage>())
        .Subscribe(bitmapImages.Add, 
        () =>
        {
            BitmapImages = bitmapImages;
        });
}

private IObservable<BitmapImage> LoadImageAsync(Uri uri)
{
    return Observable.CreateWithDisposable<BitmapImage>(observer =>
    {
        var downloader = new WebClient();
        downloader.OpenReadCompleted += (s, e) =>
        {
            if (e.Error != null)
            {
                observer.OnError(e.Error);
            }

            var bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = e.Result;
            bitmapImage.EndInit();

            observer.OnNext(bitmapImage);
            observer.OnCompleted();
        };
        downloader.OpenReadAsync(uri);

        return downloader;
    });
}

Upvotes: 0

Blindy
Blindy

Reputation: 67380

I'd use something like this:

class ImageManager
{
  private Dictionary<string, Image> images=
    new Dictionary<string,Image>();

  public Image get(string s) {  // blocking call, returns the image
    return load(s);
  }

  private Image load(string s) {  // internal, thread-safe helper
    lock(images) {
      if(!images.ContainsKey(s)) {
        Image img=// load the image s
        images.Add(s,img);
        return img; 
      }
      return images[s];
    }
  }

  public void preload(params string[] imgs) {  // non-blocking preloading call
    foreach(string img in imgs) { 
      BackgroundWorker bw=new BackgroundWorker();
      bw.DoWork+=(s,e)=>{ load(img); }  // discard the actual image return
      bw.RunWorkerAsync();
    }
  }
}

// in your main function
{
   ImageManager im=new ImageManager();
   im.preload("path1", "path2", "path3", "path4"); // non-blocking call

   // then you just request images based on their path
   // they'll become available as they are loaded
   // or if you request an image before it's queued to be loaded asynchronously 
   // it will get loaded synchronously instead, thus with priority because it's needed
}

Upvotes: 2

Piotr Justyna
Piotr Justyna

Reputation: 4966

Marcel,

WPF provides us already with great mechanisms of BackgroundWorker and Dispatcher to make you forget about writing your own thread mechanisms. However your problem doesn't seem to be that obvious for me. How many images do you need/where do you get these from? Please give us more info.

Upvotes: 0

Nick Butler
Nick Butler

Reputation: 24383

That certainly sounds like a good use for a background thread. Also, as your unit of work is quite large, there shouldn't be too much contention for the synchronization on your collections. You might find examples of similar algorithms, but I think you'll have to roll your own implementation - it's not that complicated.

One thing springs to mind, though: you will either have to keep a record of which images are currently in the process of being loaded, or tolerate multiple loads of the same image.

For example, if your UI requires an image that has not yet been loaded, you will probably want to load that image as a priority. If you know that the background thread is in the process of loading that image, you could just wait for it to become available. If instead you decide to just do the load on the UI thread, there is a possibility that the background thread will try to add a loaded image that is already present.

So, there will have to be some synchronization, but it shouldn't be too complicated.

Nick

Upvotes: 0

Related Questions