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

时间:2010-05-03 15:18:15

标签: c# .net wpf exception-handling bitmapsource

我试图找出如何将DispatcherObject(在我的案例中为BitmapSource)复制到另一个线程中。

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

有了这个,我有了WPFMessageBox的实现,它是通过设置一些静态属性来配置的 - 其中一个是BitmapSource for icon。这样使用,以便在启动时我可以设置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时,即使冻结,我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#

4 个答案:

答案 0 :(得分:7)

调用Freeze后,它应该可以在多个线程上运行。

答案 1 :(得分: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 :(得分:3)

bitmapSourceForOtherThread = new WriteableBitmap(previousBitmapSource);

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

Long answer

答案 3 :(得分: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;
}