1

I'm trying to load several images async into the ui.

When an image is loaded (a bitmap is created form a path), the image should be set as the fill of a rectangle on the window.

When I put the creation of the bitmapimage also in the Dispatcher.Invoke method, the code works. But obviously I want the heavy work (creation of the bitmap) done in the tread and not in the invoke.

I've tried several solutions including backgroundworker but I can't get it to work. Right now I have the following code:

private void Window_ContentRendered(object sender, EventArgs e)
{
    Thread loadThread = new Thread(new ThreadStart(LoadImagesAsync));
    loadThread.Start();
}

private void LoadImagesAsync()
{
    IEnumerable<string> images = System.IO.Directory.GetFiles(IMAGE_FOLDER, "*.jpg").Skip(_PageNumber * NUMBER_OF_IMAGES).Take(NUMBER_OF_IMAGES);

    for (int i = 0; i < NUMBER_OF_IMAGES; i++)
    {
        var bitm = new BitmapImage(new Uri(images.ElementAt(i)));                

        this.Dispatcher.Invoke(() =>
        {
            Grid grid = (Grid)grd_photoBox.Children[i];

            var rectangle = (from e in grid.Children.OfType<Rectangle>()
                                where e is Rectangle
                                select e).First();

            ImageBrush brush = new ImageBrush(bitm);

            rectangle.Fill = brush;
        });
    }
}

I get the following exception:

The calling thread cannot access this object because a different thread owns it.

Any clues?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
middelpat
  • 2,555
  • 1
  • 20
  • 29

3 Answers3

3

In case of big images, use the following code, it will directly rescale the images and load the in other thread then the main thread which makes sure the images are loaded instantly. Edit the following values to change the load format:

bitm.DecodePixelWidth = 200;
bitm.DecodePixelHeight = 100;

private void LoadImagesAsync()
        {
            IEnumerable<string> images = System.IO.Directory.GetFiles(IMAGE_FOLDER, "*.jpg").Skip(_PageNumber * NUMBER_OF_IMAGES).Take(NUMBER_OF_IMAGES);

            for (int i = 0; i < NUMBER_OF_IMAGES; i++)
            {
                int j = i;
                var bitm = new BitmapImage();

                bitm.BeginInit();
                bitm.CacheOption = BitmapCacheOption.OnLoad;
                bitm.UriSource = new Uri(images.ElementAt(i));
                bitm.DecodePixelWidth = 200;
                bitm.DecodePixelHeight = 100;
                bitm.EndInit();

                ImageBrush brush = new ImageBrush(bitm);
                brush.Freeze();

                this.Dispatcher.BeginInvoke(new Action(() => 
                {
                    Grid grid = (Grid)grd_photoBox.Children[j];

                    var rectangle = (from e in grid.Children.OfType<Rectangle>()
                                        where e is Rectangle
                                        select e).First();

                    rectangle.Fill = brush;
                }));
            }
        }
SynerCoder
  • 12,493
  • 4
  • 47
  • 78
  • 1
    Be careful with setting `DecodePixelWidth` or `DecodePixelHeight`, as it may degrade performance in certain scenarios. See Remarks [here](http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.bitmapimage.decodepixelwidth.aspx): `The JPEG and Portable Network Graphics (PNG) codecs natively decode the image to the specified size; other codecs decode the image at its original size and scale the image to the desired size`. And you shouldn't usually set both if you want to preserve the image's native aspect ratio. – Clemens Mar 20 '13 at 17:49
  • It can also be noted that if there is a need to call a method marked as async, then the action have to be marked async as well, `new Action(async () =>` – GaelSa Mar 21 '17 at 09:46
1

The trick is to freeze the bitmap to allow access from another thread. Therefore you also have to make sure that the bitmap is loaded immediately on creation, as the default behaviour is to load lazily.

var bitm = new BitmapImage();
bitm.BeginInit();
bitm.CacheOption = BitmapCacheOption.OnLoad; // load immediately
bitm.UriSource = new Uri(images.ElementAt(i));
bitm.EndInit();
bitm.Freeze();
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • The default behaviour is `BitmapCacheOption.Default` which is according to [MSDN](http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.bitmapcacheoption.aspx) "Caches the entire image into memory. " – SynerCoder Mar 20 '13 at 09:25
  • @SynerCoder See the Remarks section [here](http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.bitmapimage.cacheoption.aspx). Although it describes the behaviour when loading the image from stream, it is generally applicable. And [here](http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.bitmapcacheoption.aspx) you can read that `OnLoad` "caches the entire image into memory **at load time**", in contrast to "caches the entire image into memory" for `Default`. – Clemens Mar 20 '13 at 17:32
  • @SynerCoder However, you do not ultimately need to apply the flag here, as the images are loaded from file, which seems to be done immediately. You could check the [IsDownloading](http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.bitmapimage.isdownloading.aspx) property to find out if the image is beeing download or not. As long as it is downloading, you can't freeze it. – Clemens Mar 20 '13 at 17:44
  • 1
    Point taken. Good point and i'll also incorporate it in my answer. Combining the best! – SynerCoder Mar 21 '13 at 09:12
0

I tried below code and it is working for me.

Task<IEnumerable<string>>.Factory.StartNew(() => System.IO.Directory.GetFiles(
            imagePath,
            "*.jpg")).
            ContinueWith(task =>
                        {
                            foreach (var item in task.Result)
                            {
                                this.Dispatcher.BeginInvoke((Action)(() =>
                                {
                                    var img = new Image
                                                    {
                                                        Source =
                                                            new BitmapImage(
                                                            new Uri(item))
                                                    };
                                    LayoutRoot.Children.Add(img);

                                }));

                            }
                        });

LayoutRoot is my grid in xaml.

Hope it will help.

Mukesh Rawat
  • 2,047
  • 16
  • 30
  • Thank you for your answer. It does work, but still the bitmap is created form uri in the Invoke. This causes the bitmap to be created in the UI thread. Using large images this still causes the UI te block – middelpat Mar 19 '13 at 18:45