如何将DispatcherObject(BitmapSource)复制到不同的线程?

5
我正在尝试找出如何将DispatcherObject(在我的情况下是BitmapSource)复制到另一个线程中。
用例:
我有一个WPF应用程序,需要在新线程中显示窗口(实际上是Outlook插件,我们需要这样做是因为Outlook在主UI线程中有一些钩子,并且窃取了某些我们需要使用的热键 - 在Outlook,WPF(我们用于UI)和Winforms(我们需要使用某些由Microsoft提供的Winforms控件)的互操作中“失落在翻译中”)。
因此,我有自己的WPFMessageBox实现,通过设置一些静态属性进行配置 - 其中之一是用于图标的BitmapSource。 这是为了在启动时可以设置WPFMessageBox.Icon一次,从那时起,每个WPFMessageBox都将具有相同的图标。
问题在于,分配给图标的BitmapSource是一个DispatcherObject,当读取时,它会引发InvalidOperationException:“调用线程无法访问此对象,因为不同的线程拥有它”。
如何将该BitmapSource克隆到实际线程中? 它具有Clone()和CloneCurrentValue()方法,但它们不起作用(它们也会抛出相同的异常)。 我还想到了使用originalIcon.Dispatcher.Invoke(在此处进行克隆)-但是BitmapSource的Dispatcher为null,并且仍然-我将在错误的线程上创建副本,仍然无法在我的线程上使用它。 BitmapSource.IsFrozen == true。
有没有想法如何将BitmapSource复制到不同的线程中(而无需完全从图像文件中在新线程中重构它)?
编辑: 因此,冻结没有帮助:最终我有一个BitmapFrame(Window.Icon不需要任何其他类型的ImageSource),当我将其分配为不同线程上的Window.Icon时,即使已冻结,我也会收到InvalidOperationException:“调用线程无法访问此对象,因为不同的线程拥有它。”,并附带以下堆栈跟踪:
    WindowsBase.dll!System.Windows.Threading.Dispatcher.VerifyAccess() + 0x4a bytes 
    WindowsBase.dll!System.Windows.Threading.DispatcherObject.VerifyAccess() + 0xc bytes    
    PresentationCore.dll!System.Windows.Media.Imaging.BitmapDecoder.Frames.get() + 0xe bytes    
    PresentationFramework.dll!MS.Internal.AppModel.IconHelper.GetIconHandlesFromBitmapFrame(object callingObj = {WPFControls.WPFMBox.WpfMessageBoxWindow: header}, System.Windows.Media.Imaging.BitmapFrame bf = {System.Windows.Media.Imaging.BitmapFrameDecode}, ref MS.Win32.NativeMethods.IconHandle largeIconHandle = {MS.Win32.NativeMethods.IconHandle}, ref MS.Win32.NativeMethods.IconHandle smallIconHandle = {MS.Win32.NativeMethods.IconHandle}) + 0x3b bytes   
>   PresentationFramework.dll!System.Windows.Window.UpdateIcon() + 0x118 bytes  
    PresentationFramework.dll!System.Windows.Window.SetupInitialState(double requestedTop = NaN, double requestedLeft = NaN, double requestedWidth = 560.0, double requestedHeight = NaN) + 0x8a bytes  
    PresentationFramework.dll!System.Windows.Window.CreateSourceWindowImpl() + 0x19b bytes  
    PresentationFramework.dll!System.Windows.Window.SafeCreateWindow() + 0x29 bytes 
    PresentationFramework.dll!System.Windows.Window.ShowHelper(object booleanBox) + 0x81 bytes  
    PresentationFramework.dll!System.Windows.Window.Show() + 0x48 bytes 
    PresentationFramework.dll!System.Windows.Window.ShowDialog() + 0x29f bytes  
    WPFControls.dll!WPFControls.WPFMBox.WpfMessageBox.ShowDialog(System.Windows.Window owner = {WPFControlsTest.MainWindow}) Line 185 + 0x10 bytes  C#
4个回答

8

一旦你调用了Freeze,它应该可以在多个线程上工作。


谢谢 - 我之前尝试过那个,但其他条件抛出了异常,在清理代码后,Freeze() 就足够了。 - Tomáš Kafka
好的,抱歉 - 看起来冻结BitmapFrame不允许它作为窗口图标在不同的线程中使用 - 请参见我对问题的编辑。 - Tomáš Kafka
我试图在一个线程中生成位图,在另一个线程中保存它,而在这种情况下,Freeze() 起作用。 - paulmelnikow

5

bitmapSourceForOtherThread = new WriteableBitmap(previousBitmapSource);

这是有代价的,但与序列化相比还是很便宜的。

长篇回答


对于我的情况,这是唯一有效的解决方案。我需要将一个 BitmapFrame 输入 TransformedBitmap 中,但是因为它是由库提供的(并且 BitmapFrame 的 Dispatcher 属性为空),所以无法控制 BitmapFrame 的创建线程。即使查询 CanFreeze 属性也会导致异常。我有两个选择:彻底重构我的和他人的源代码,或者使用 WriteableBitmap。猜猜我选择了哪一个... - user2819245
我无法使用新的WriteableBitmap来克隆在另一个线程上创建的WriteableBitmap。 - paulmelnikow
@noa:是什么错误/异常?你看了博客上的代码吗? - bohdan_trotsenko
这个异常是这样的:System.InvalidOperationException: 调用线程无法访问此对象,因为不同的线程拥有它。我看了一下代码,它看起来像是最初的bitmapSource从一个文件中加载的。 - paulmelnikow
顺便说一下,在调用new WriteableBitmap时引发了异常。 - paulmelnikow
你能否提供一个最小的示例来展示在你的情境下new WriteableBitmap 失败的情况? - bohdan_trotsenko

3
关键是要在你想要使用的线程上创建位图。因此,你不能将图标缓存到某些静态字段/属性中,而是每次打开新窗口时从文件、资源、流或其他位置加载它。
BitmapFrame只能在创建它的线程上使用。
即使克隆也无法解决这个问题,正如你所正确指出的那样(这真是太糟糕了)。
我曾经遇到过完全相同的问题,并通过每次加载图标来解决它,在我特定的情况下只需调用
// get your stream somewhere - 
window.Icon = BitmapFrame.Create(stream)

以下是如何在WPF中从资源中获取图标的方法:

var streamResourceInfo = Application.GetResourceStream(new Uri(@"pack://application:,,,/YourAssembly;relative path to the icon", UriKind.RelativeOrAbsolute));
// use streamResourceInfo.Stream 

2

有一个可行的解决方法,虽然性能不是很好,就是从图像数据创建一个内存流,然后在你想要使用它的线程上重新构建图像。

BitmapSource 的示例:

Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)delegate()
{
    //serialize image on UI thread
    imageStream = GetImageBytes(cameraImage);
}

...
//reconstruct image on a different thread:
Bitmap bitmap = new Bitmap(imageStream); 

private MemoryStream GetImageBytes(BitmapSource image)
{
    MemoryStream ms = new MemoryStream();
    BitmapEncoder encoder = new PngBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(image));
    encoder.Save(ms);
    ms.Seek(0, SeekOrigin.Begin);
    return ms;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接