如何在 C# 的后台线程中创建 BitmapImage 缓冲区?

How to create a BitmapImage buffer in background thread on C#?

您好,我想创建一个 BitmapImage 缓冲区,该缓冲区是使用来自后台工作人员的队列构建的。主要目标是从私有网络加载一些图像并且不要阻止UI。我可以在不阻塞 UI 的情况下创建该缓冲区,但是当我尝试从队列中获取一张图像时,我得到一个 System.InvalidOperationException,因此我正在尝试访问另一个线程拥有的对象。

我的员工代码:

        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;
            });
        }

调用方式:

        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线程调用方法:

        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");
            //});
        }

我尝试在 BufferLoadingCompleted() 上使用 Dispatcher.Invoke 但它不起作用,我得到了同样的异常。我做错了什么?

最终代码。 Andy 建议的解决方案: 在我的后台工作程序代码中,我没有 Freeze() 在工作线程中创建新对象,所以我遇到了异常。

解决方案仅适用于后台工作者方法:

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;
    });
}

您正在非 ui 线程上创建默认具有线程关联的内容。

幸运的是,位图图像继承自 freezable。 查看继承链:

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

继承: 目的 调度器对象 依赖对象 可冻结 动画化 图片来源 位图源 位图图像

如果您在可冻结对象上调用 .Freeze(),那么您可以在线程之间传递它。

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

从那里:

什么是可冻结对象?

Freezable 是一种特殊类型的对象,它具有两种状态:未冻结和已冻结。解冻时,Freezable 的行为与任何其他对象一样。冻结后,无法再修改 Freezable。

Freezable 提供了 Changed 事件来通知观察者对象的任何修改。冻结 Freezable 可以提高其性能,因为它不再需要将资源花费在更改通知上。 冻结的 Freezable 也可以跨线程共享,而未冻结的 Freezable 则不能。