0

Here's my problem.
I'm loading a few BitmapImages in a BlockingCollection

    public void blockingProducer(BitmapImage imgBSource)
    {
        if (!collection.IsAddingCompleted)
            collection.Add(imgBSource);
    }

the loading happens in a backgroungwork thread.

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        String filepath; int imgCount = 0;

        for (int i = 1; i < 10; i++)
        {
            imgCount++;

            filepath = "Snap";
            filepath += imgCount;
            filepath += ".bmp";

            this.Dispatcher.BeginInvoke(new Action(() =>
            {
                label1.Content = "Snap" + imgCount + " loaded.";
            }), DispatcherPriority.Normal);

            BitmapImage imgSource = new BitmapImage();
            imgSource.BeginInit();
            imgSource.UriSource = new Uri(filepath, UriKind.Relative);
            imgSource.CacheOption = BitmapCacheOption.OnLoad;
            imgSource.EndInit();

            blockingProducer(imgSource);
        }
    }

debugging this part of the code everything looks okay, the problem comes now ...

after finishing loading the images I want to show them in UI one by one. I'm using a dispatcher to do so but I always get the message telling me that the called Thread can not access the object because it belongs to a different Thread.

    public void display(BlockingCollection<BitmapImage> results)
    {
        foreach (BitmapImage item in collection.GetConsumingEnumerable())
        {
            this.Dispatcher.BeginInvoke(new Action(() =>
            {
                this.dstSource.Source = item;
                Thread.Sleep(250);
            }), DispatcherPriority.Background);
        }
    }

debug accuses that the error is here

                this.dstSource.Source = item;

I'm trying everything but cant find out what’s wrong. Anyone has any idea?

Anthon
  • 69,918
  • 32
  • 186
  • 246
Probst
  • 17
  • 1
  • 8

2 Answers2

1

You have to call Freeze after loading the images in order to make them accessible to other threads:

BitmapImage imgSource = new BitmapImage();
imgSource.BeginInit();
imgSource.UriSource = new Uri(filepath, UriKind.Relative);
imgSource.CacheOption = BitmapCacheOption.OnLoad;
imgSource.EndInit();
imgSource.Freeze(); // here

As far as I have understood the BitmapCacheOption.OnLoad flag, it is only effective when a BitmapImage is loaded from a stream. The Remarks section in BitmapCacheOption says:

Set the CacheOption to BitmapCacheOption.OnLoad if you wish to close a stream used to create the BitmapImage. The default OnDemand cache option retains access to the stream until the image is needed, and cleanup is handled by the garbage collector.

A BitmapImage created from a Uri may be loaded asynchronously (see the IsDownloading property). Consequently, Freeze may not be callable on such a BitmapImage, as downloading may still be in progress after EndInit. I guess it nevertheless works in your case because you are loading BitmapImages from file Uris, which seems to be done immediately.

To avoid this potential problem you may just create the BitmapImage from a FileStream:

var imgSource = new BitmapImage();

using (var stream = new FileStream(filepath, FileMode.Open))
{
    imgSource.BeginInit();
    imgSource.StreamSource = stream;
    imgSource.CacheOption = BitmapCacheOption.OnLoad;
    imgSource.EndInit();
    imgSource.Freeze();
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Hi, thank you it works. One last issue. After loading the images into the BlockingCollection, I start displaying them but only the last image is shown. Any idea of what is happening? Thank you. – Probst Apr 18 '13 at 13:12
  • That is certainly because you do the Sleep call inside the delegate method that is passed to the Dispatcher. You would have to call it before BeginInvoke. However, a better solution would be to use a [DispatcherTimer](http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatchertimer.aspx) to show the images one by one. – Clemens Apr 18 '13 at 15:31
  • Hi Clemens, I removed the Sleep but it still does not work. Strange fact is that when I call the Load Thread for the second time the images are displayed. But once I call the display function nothing happens. So the first time that I run the program only the last image is displayed. From now on every time I call the Load Thread the images are displayed and when I call the Display Thread nothing happens. – Probst Apr 18 '13 at 15:52
  • Try to pull and display the next image in a DispatcherTimer. – Clemens Apr 18 '13 at 16:06
  • Hi Clemens .. I tried out the DispatcherTimer and now nothing seems to work. I posted the code below. Any idea? Thank you. – Probst Apr 18 '13 at 17:06
0

For the further future readers, here is the code I used to fix my problem.

    public void display(BlockingCollection<BitmapImage> collection)
    {
        if (collection.IsCompleted || collection.Count != 0)
        {
            BitmapImage item = collection.Take();
            this.Dispatcher.BeginInvoke(new Action(() =>
            {
                this.dstSource.Source = item;

            }), DispatcherPriority.Normal);
        }
        else
        {
            dispatcherTimer.Stop();
        }
    }

    public void dispatcherTimer_Tick(object sender, EventArgs e)
    {
        display(collection);
    }

    public void configureDispatcherTimer()
    {
        dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
        TimeSpan interval = TimeSpan.FromMilliseconds(150);
        dispatcherTimer.Interval = interval;
    }
Probst
  • 17
  • 1
  • 8