views:

353

answers:

3

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:

  • LinkedList<string> to stores pathes to the pictures and navigate to the next picture
  • Dictionary<string, BitmapImage> to store the preloaded images
A: 

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

Nick Butler
Just synchronization wasn't enought. I ran into InvalidOperationExceptions, because the collection is owned by the background thread.
Marcel Benthin
Collections like LinkedList and Dictionary aren't "owned" by any thread. What threw an InvalidOperationException?
Nick Butler
If such collection is bound to UI, it is actually owned bu UI thread.
Piotr Justyna
A: 

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.

Piotr Justyna
Mostly it should not be more than 40-50 pictures, but i would like to be prepeared if there are more. The Images come from a folder on the HDD.
Marcel Benthin
Hm, if images come from HDD there really should be no dalays. However, if these images are quite big, you might want to prepare smaller thumbs and display these instead and after a small thumb is shown you can start the background worker to get you an original image (to be prepared, that user wants to enlarge the thumb). What do you say?
Piotr Justyna
+1  A: 

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
}
Blindy
This looks really good, but still it does not prevent from waiting. Consider such example: we have 100 images available to be downloaded from e.g. FTP. If user clicks the last image, he will be forced to wait until all 99 previous images are downloaded. In this scenario this solution has only one pro: it does not block the UI. Marcel, please provide us with more info :)
Piotr Justyna
If you click the 100'th image, it will be loaded in the ui thread on demand, independent of the queued images, so it will be shown as soon as possible even if the preloader didn't reach it yet.
Blindy
So what's the point of using such preloader? :)
Piotr Justyna
If the application has idle time, it will preload everything. It just lets you prioritize what you need right this moment over stuff you may or may not need.
Blindy
Oh, you just won't win this. Why would we want the application to download images in background in case the user wants to see only one of them?
Piotr Justyna
Works great ... Didn't know about the Backgroundworker. Nice to know and doesn't need much synchronization, which i find pleasing to read.
Marcel Benthin
@piotr, that was the requirement, background loading of images. What my example does in addition to the requirement clearly stated in the original post, is that it doesn't just die in case the user wants a specific image that hasn't been loaded yet, it loads it with priority then keeps going with the rest.
Blindy
Is it realy necessary to instantiate a new BackgroundWorker for every Image?
Marcel Benthin
I imagine it has a thread pool of sorts behind it to not bog down. If not, replace the calls to it with `ThreadPool.QueueUserWorkItem(o=>{ load(img); }`.
Blindy