Reputation: 3674
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 pictureDictionary<string, BitmapImage>
to store the preloaded imagesUpvotes: 1
Views: 4756
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
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
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
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