【问题标题】:How to copy DispatcherObject (BitmapSource) into different thread?如何将 DispatcherObject (BitmapSource) 复制到不同的线程中?
【发布时间】:2011-02-15 02:07:40
【问题描述】:

我正在尝试弄清楚如何将 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 时,即使被冻结,我获取 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#

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

    • 谢谢-我之前试过了,但是其他条件都抛出了异常,清理代码后,Freeze()就足够了。
    • 好的,抱歉 - 冻结 BitmapFrame 似乎不允许它在不同的线程中用作窗口图标 - 请参阅我的问题编辑。
    • 我试图在一个线程中生成位图,然后将其保存在另一个线程中,在这种情况下,Freeze() 有效。
    【解决方案2】:

    关键是在要使用的线程上创建位图。所以你不能将你的图标缓存在一些静态字段/属性中,每次在新线程上打开一个新窗口时(从文件、资源、流或其他)加载它。

    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 
    

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        bitmapSourceForOtherThread = new WriteableBitmap(previousBitmapSource);

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

        Long answer.

        【讨论】:

        • 就我而言,这是唯一有效的解决方案。我需要将 BitmapFrame 输入到 TransformedBitmap 中。无法控制在哪个线程上创建 BitmapFrame,因为它由库提供(并且 BitmapFrame 的 Dispatcher 属性为空)。即使查询 CanFreeze 属性也会导致异常。我有两个选择:对我和其他人的源代码进行大修,或者使用 WriteableBitmap。猜猜我选择了哪一个...
        • 我无法使用新的 WriteableBitmap 克隆在另一个线程上创建的 WriteableBitmap。
        • @noa:错误/异常是什么?你看博客上的代码了吗?
        • 例外情况如下: System.InvalidOperationException:调用线程无法访问此对象,因为不同的线程拥有它。我确实看过代码。看起来您的原始 bitmapSource 是从文件加载的。
        • 顺便说一句,在调用new WriteableBitmap时引发了异常。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-08
        • 2011-11-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多