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

Posted

技术标签:

【中文标题】如何在 C# 的后台线程中创建 BitmapImage 缓冲区? [复制]【英文标题】:How to create a BitmapImage buffer in background thread on C#? [duplicate] 【发布时间】:2021-11-21 04:56:49 【问题描述】:

您好,我想创建一个 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,但它不起作用我得到了同样的异常。我做错了什么?

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

解决方案仅适用于后台worker方法:

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

【问题讨论】:

【参考方案1】:

您在默认情况下具有线程关联性的非 ui 线程上创建一些东西。

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

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。

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

【讨论】:

感谢@Andy,您帮助我解决了这个问题并学习了新知识。

以上是关于如何在 C# 的后台线程中创建 BitmapImage 缓冲区? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

在 CoreData 的后台线程中创建实体

在 C# 中创建动态线程 [关闭]

Qt5中创建临时的后台线程。

在 C# 中创建加密随机数的最快、线程安全的方法?

如何在 iOS 中创建服务?

Godot C# 在后台运行