Home > Back-end >  How to create a BitmapImage buffer in background thread on C#? [duplicate]
How to create a BitmapImage buffer in background thread on C#? [duplicate]

Time:09-29

Hello I want to create a buffer of BitmapImage built with a Queue from a background worker. The main target is to load some images from a private network and don't block UI. I can create that buffer without blocking UI but when I try to get one image from the queue I get an System.InvalidOperationException, so I'm trying to access an object owned by another thread.

My worker code:

        private Task<Queue<BitmapImage>> BufferLoader(List<ReadLabel> labels)
        {
            return Task<Queue<BitmapImage>>.Factory.StartNew(() =>
            {
                var parameters = CommonData.CurrentRecipe.Parameters["validation"];
                var buffer = new Queue<BitmapImage>();

                foreach (var label in labels)
                {
                    var fileName = $"{CommonData.CurrentFiche.RegId}-{label.ReadPosition}.bmp";
                    var validationPath = parameters[ValidationParameters.Parameters.sValidationImagesPath.ToString()].Value.ToString();
                    var fullPath = Path.Combine(Properties.Settings.Default.CameraImagesBasePath, validationPath, fileName);

                    try
                    {
                        if (File.Exists(fullPath))
                        {
                            buffer.Enqueue(new BitmapImage(new Uri(fullPath, UriKind.Absolute)));
                        }
                        else
                        {
                            throw new ValidationImageNotFoundException(fullPath);
                        }
                    }
                    catch { }
                }

                return buffer;
            });
        }

Calling method:

        private async void LoadValidationImages()
        {
            var imageList = new List<ReadLabel>(CommonData.CurrentFiche.Labels);
            var images = imageList.FindAll(f => f.CoscNumber.StartsWith("err"));

            if (images.Count > 0)
            {
                Queue<BitmapImage> result = await BufferLoader(images);

                ImageBuffer = new Queue<BitmapImage>(result);

                BufferLoadingCompleted();
            }
        }

UI thread call method:

        private void BufferLoadingCompleted()
        {
            /*Dispatcher.Invoke(() =>
            {*/
                imgToValidate.Source = ImageBuffer.Peek();

                var parameters = CommonData.CurrentRecipe.Parameters["validation"];
                rotation = parameters[ValidationParameters.Parameters.ImageRotation.ToString()].ValueToDouble();
                scaleX = parameters[ValidationParameters.Parameters.ImageScaleX.ToString()].ValueToDouble();
                scaleY = parameters[ValidationParameters.Parameters.ImageScaleY.ToString()].ValueToDouble();
                scrlImage.ScrollToHorizontalOffset(parameters[ValidationParameters.Parameters.ScrollerHorizontalFactor.ToString()].ValueToDouble());
                scrlImage.ScrollToVerticalOffset(scrollPosVertical = parameters[ValidationParameters.Parameters.ScrollerVerticalFactor.ToString()].ValueToDouble());

                ApplyTransformations();

                Console.WriteLine("Load finished");
            //});
        }

I tried to use Dispatcher.Invoke on BufferLoadingCompleted() but it don't work I get the same exception. What I'm doing wrong?

Final code. Solution as suggested by Andy: In my background worker code I didn't Freeze() the new objects created inside the working thread, so I was getting an exception.

Solution applies only to the background worker method:

private Task<Queue<BitmapImage>> BufferLoader(List<ReadLabel> labels)
{
    return Task<Queue<BitmapImage>>.Factory.StartNew(() =>
    {
        var parameters = CommonData.CurrentRecipe.Parameters["validation"];
        var buffer = new Queue<BitmapImage>();

        foreach (var label in labels)
        {
            var fileName = $"{CommonData.CurrentFiche.RegId}-{label.ReadPosition}.bmp";
            var validationPath = parameters[ValidationParameters.Parameters.sValidationImagesPath.ToString()].Value.ToString();
            var fullPath = Path.Combine(Properties.Settings.Default.CameraImagesBasePath, validationPath, fileName);

            try
            {
                if (File.Exists(fullPath))
                {
                    var newImage = new BitmapImage(new Uri(fullPath, UriKind.Absolute));
                    newImage.Freeze();

                    buffer.Enqueue(newImage);
                }
                else
                {
                    throw new ValidationImageNotFoundException(fullPath);
                }
            }
            catch { }
        }

        return buffer;
    });
}

CodePudding user response:

You're creating something on a non ui thread that by default has thread affinity.

Luckily though, a bitmapimage inherits from freezable. See the inheritance chain:

https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.imaging.bitmapimage?view=net-5.0

Inheritance: Object DispatcherObject DependencyObject Freezable Animatable ImageSource BitmapSource BitmapImage

If you call .Freeze() on a freezable then you can pass it between threads.

https://docs.microsoft.com/en-us/dotnet/desktop/wpf/advanced/freezable-objects-overview?view=netframeworkdesktop-4.8

From there:

What Is a Freezable?

A Freezable is a special type of object that has two states: unfrozen and frozen. When unfrozen, a Freezable appears to behave like any other object. When frozen, a Freezable can no longer be modified.

A Freezable provides a Changed event to notify observers of any modifications to the object. Freezing a Freezable can improve its performance, because it no longer needs to spend resources on change notifications. A frozen Freezable can also be shared across threads, while an unfrozen Freezable cannot.

  • Related