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

发布于 2024-08-31 00:11:22 字数 2656 浏览 8 评论 0原文

我试图弄清楚如何将 DispatcherObject (在我的例子中为 BitmapSource)复制到另一个线程中。

使用案例:
我有一个 WPF 应用程序需要在新线程中显示窗口(该应用程序实际上是 Outlook 插件,我们需要这样做,因为 Outlook 在主 UI 线程中有一些挂钩,并且正在窃取我们需要使用的某些热键 - '丢失Outlook、WPF(我们用于 UI)和 Winforms(我们需要使用某些 microsoft 提供的 winforms 控件)的互操作中的翻译。

这样,我就有了 WPFMessageBox 的实现,它是通过设置一些静态属性来配置的 - 其中之一是图标的 BitmapSource。使用它是为了在启动时我可以设置 WPFMessageBox.Icon 一次,从那时起,每个 WPFMessageBox 将具有相同的图标。

问题在于,分配到 icon 中的 BitmapSource 是一个 DispatcherObject,读取时会抛出 InvalidOperationException:“调用线程无法访问此对象,因为不同的线程拥有它。”。

如何将该 BitmapSource 克隆到实际线程中?它有 Clone() 和 CloneCurrentValue() 方法,但它们不起作用(它们也抛出相同的异常)。我还想到使用originalIcon.Dispatcher.Invoke(在此处进行克隆) - 但 BitmapSource 的 Dispatcher 为空,并且仍然 - 我会在错误的线程上创建一个副本,但仍然无法在我的线程上使用它。 BitmapSource.IsFrozen == true。

关于如何将 BitmapSource 复制到不同线程中(无需从新线程中的图像文件完全重建它)有什么想法吗?

编辑: 所以,冻结没有帮助:最后我有一个 BitmapFrame (Window.Icon 无论如何都不采用任何其他类型的 ImageSource),当我将其分配为不同线程上的 Window.Icon 时,即使冻结,我get 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#

I am trying to figure out how can I copy DispatcherObject (in my case BitmapSource) into another thread.

Use case:
I have a WPF app that needs to show window in a new thread (the app is actually Outlook addin and we need to do this because Outlook has some hooks in the main UI thread and is stealing certain hotkeys that we need to use - 'lost in translation' in interop of Outlook, WPF (which we use for UI), and Winforms (we need to use certain microsoft-provided winforms controls)).

With that, I have my implementation of WPFMessageBox, that is configured by setting some static properties - and and one of them is BitmapSource for icon. This is used so that in startup I can set WPFMessageBox.Icon once, and since then, every WPFMessageBox will have the same icon.

The problem is that BitmapSource, which is assigned into icon, is a DispatcherObject, and when read, it will throw InvalidOperationException: "The calling thread cannot access this object because a different thread owns it.".

How can I clone that BitmapSource into the actual thread? It has Clone() and CloneCurrentValue() methods, which don't work (they throw the same exception as well). It also occured to me to use originalIcon.Dispatcher.Invoke( do the cloning here ) - but the BitmapSource's Dispatcher is null, and still - I'd create a copy on a wrong thread and still couldnt use it on mine. BitmapSource.IsFrozen == true.

Any idea on how to copy the BitmapSource into different thread (without completely reconstructing it from an image file in a new thread)?

EDIT:
So, freezing does not help: In the end I have a BitmapFrame (Window.Icon doesn't take any other kind of ImageSource anyway), and when I assign it as a Window.Icon on a different thread, even if frozen, I get InvalidOperationException: "The calling thread cannot access this object because a different thread owns it." with a following stack trace:

    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#

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

鱼窥荷 2024-09-07 00:11:22

一旦调用Freeze,它就应该在多个线程上运行。

Once you call Freeze, it should work on multiple threads.

少女净妖师 2024-09-07 00:11:22

bitmapSourceForOtherThread = new WriteableBitmap(previousBitmapSource);

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

长答案

bitmapSourceForOtherThread = new WriteableBitmap(previousBitmapSource);

This comes at a price, but it's pretty cheap comparing to serializing.

Long answer.

东北女汉子 2024-09-07 00:11:22

关键是在您要使用的线程上创建位图。因此,您无法将图标缓存在某些静态字段/属性中,而是每次在新线程上打开新窗口时加载它(从文件、资源、流或其他内容)。

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 

The key is to create the bitmap on the thread you want to use. So you can't cache your icon in some static field/property, bud load it (from file, resource, stream or whatever) every time you're opening a new window on new thread.

BitmapFrame can be used on the thread it was created only.

Even cloning doesn't work here, as you correctly stated (which just sucks).

I had exactly the same problem and solved it just by loading icon every time, in my particular case simply by calling

// get your stream somewhere - 
window.Icon = BitmapFrame.Create(stream)

And this is how you can get your icon from resource in WPF:

var streamResourceInfo = Application.GetResourceStream(new Uri(@"pack://application:,,,/YourAssembly;relative path to the icon", UriKind.RelativeOrAbsolute));
// use streamResourceInfo.Stream 
彼岸花ソ最美的依靠 2024-09-07 00:11:22

一种可行的解决方法(尽管性能不是很好)是从图像数据创建内存流,然后在要使用它的线程上重建图像。

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

One workaround that does work, although not very performant, is creating a memory stream from the image data, then reconstructing the image on the thread you want to use it on.

Example for 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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文