« BitmapImage.UriSource binding doesn't work ... | Main | WPF Rocks »

Loading BitmapSources in the Background ...

I've hit this problem several times now.

I've wanted to load an image on another thread (other than the UI thread), and then display it in a WPF UI. Bam. Doesn't work in WPF. You have to load/create the Bitmap on the UI thread that will ultimately display the image. If the images are from the network or a slow source, this can really nail performance of the application (as it pauses and hangs trying to load the Bitmaps). Here's a work-around that I'm experimenting with. I'll post a larger sample soon, but I thought I'd post this now:

 

   1:          private void DoLoadImage(object o)
   2:          {
   3:              if (!_loading) { return; }
   4:   
   5:              // this creates a lot of temporary buffers (should try to reuse them somehow...?)
   6:              byte[] buffer = File.ReadAllBytes(_filename);            
   7:              // by reading the data into an in-memory buffer
, we prevent the file from being read in the UI thread -- which speeds up
   8:              // access dramatically!
   9:              MemoryStream mem = new MemoryStream(buffer);
  10:   
  11:              if (!_loading)
  12:              {
  13:                  mem.Dispose();
  14:                  return;
  15:              }
  16:              this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, 
(DelegateZeroParam)delegate()
  17:              {
  18:                  if (!_loading) {
  19:                      mem.Dispose();
  20:                      return; 
  21:                  }
  22:   
  23:                  BitmapImage bi = new BitmapImage();
  24:                  bi.BeginInit();
  25:                  bi.DecodePixelWidth = 80;
  26:                  bi.DecodePixelHeight = 60;
  27:                  bi.StreamSource = mem;
  28:                  bi.EndInit();
  29:                  _imageSource = bi;
  30:                  _loading = false;
  31:                  RaisePropetyChanged("ImageSource");
  32:              });
  33:              
  34:              // if you dispose of the memory stream here, the image 
will be toast (burnt toast)
  35:              // (as the dispatcher won't have run yet).
  36:          }

The _loading variable I'm using to decide whether loading should still happen for this object. The object (a "Photo" in this case is a Dependency object so it has access to the Dispatcher. The trick is to load the bytes for the image in a thread, but then in the UI thread actually create the Bitmap. I do that using a slight of hand delegate declared as:

    public delegate void DelegateZeroParam();

Of course, you'd need to wrap the actual call to this method in some background loader thread to make it useful.

This technique comes at the cost of bytes, but it make the UI very snappy when compared to a UI-thread only method. (I'll post more about why I'm raising the "ImageSource" property changed in another post).

 

UPDATE: Charles just suggested I try freezing the BitmapImage. New post here.

Help support my web site by searching and buying through Amazon.com (in assocation with Amazon.com).