不能使用属于不同线程的父级Freezable的DependencyObject - prism

5
我在开发一个基于Prism的WPF应用程序时遇到了问题。 在加载任何窗口之前,应用程序使用Prism启动器引导,并在不同的线程(STA)上打开模式对话框,然后加载一堆东西(服务等)。该对话框在此期间处于打开状态,并允许通过事件聚合器传递更新来通知用户应用程序启动过程的进度。当加载完成后,引导程序关闭对话框并打开主应用程序窗口。然而,关闭应用程序时同样会出现相同的情况。主窗口被关闭后,将打开一个对话框框(同样在新的STA线程上),以允许通知。但是,当调用ShowDialog时(它发生在新的STA线程中),就会引发异常:“无法使用属于其父对象Freezable的不同线程的DependencyObject”。经过长时间的调试,我发现异常的原因是窗口的背景颜色是从应用程序级别的合并字典中(在WPF UI线程上实例化)获取的画笔/图像。如果在没有ResouceDictionary的情况下加载图像,则一切正常。
总之: 当使用resourceDictionary且在第二次调用一个新的STA线程时观察到这个异常,在调用ShowDialog时抛出异常。 如果只有一个对话框(例如在启动过程中没有对话框,仅在关闭过程中存在对话框),则不会发生异常。
我的问题是:原因是什么? 在这种情况下,这个异常具体意味着什么? (我明白一般来说其他线程的UI线程更新有一些问题,但我不明白为什么这只在第二次dialgo+thread实例上发生)。
谢谢 :)
2个回答

2
正如您所提到的,您的背景对象是在主UI线程上创建的。您的背景实际上是一个Brush对象,而Brush是一个DependencyObject。
当DependencyObject被创建时,它"依赖"于它所在的STA线程。因此,就像其他依赖对象一样,它只能在自己的线程上使用。这意味着STA和旧COM对象模型的兼容性。
因此,当您尝试在其他STA线程上使用它时,会收到适当的异常。
附注:我在定义为资源的图像中也遇到了同样的问题。

当移除第一个对话框时,这个问题仍然存在(关闭对话框仍然没有拥有该资源)。 - benchuk

1
我曾经遇到过类似的问题。我不确定您是如何实现背景的。我可以尝试解释一下我的情况,也许您可以从中得到一些启示。我创建了自己的基础窗口,我们称之为MyWindow,它继承自Window。
public class MyWindow : Window
{
}

我想要做的是将应用程序资源字典中的动态资源背景应用到页面上。我首先尝试了这个答案。
public class MyWindow : Window
    {
        public MyWindow()
        {
            this.SetResourceReference(BackgroundProperty, "MyResourceKey");
        }
    }

当资源是固定颜色时,这对我有效。

<SolidColorBrush x:Key="MyResourceKey" Color="White"/>

我发现当我将资源引用设置为系统颜色时,会出现你遇到的问题。

<SolidColorBrush x:Key="MyResourceKey"  Color="{DynamicResource {x:Static SystemColors.WindowColorKey}}"/>    

第一次它可以工作,但第二次我会得到父级可冻结错误。所以我的最初想法是,哦,这是一个线程问题,我只需要调用分派程序。现在这就是我遇到的问题,因为我认为我需要在窗口上调用它。这不是真的。你需要在该资源的依赖对象上调用它。
问题是,你没有使用SetResourceReference的对象,因为它查找资源并创建对它的引用。所以我们需要的是实际的依赖对象。要从资源中获取对象,可以这样做。

object temp = this.TryFindResource("MyResourceKey");

现在你已经有了这个对象,但它需要成为一个依赖对象。我没有尝试过,但你可能只需这样做。

DependencyObject temp = (DependencyObjet)this.TryFindResource("MyResourceKey"); 

现在你有了依赖对象!这就是导致我们线程问题的自由zable父级。现在我们在此对象上调用分派程序。最终我得到了类似于这样的东西。这对我有用,但我可能会尝试稍微整理一下。

public class MyWindow: Window
    {
        public MyWindow()
        {
            SetResources();                
        }

        private void SetResources()
        {
            DependencyObject dependencyObject;
            object temp;

                temp = this.TryFindResource("MyResourceKey");

                if (temp != null)
                {
                    if (temp is DependencyObject)
                    {
                        dependencyObject = (DependencyObject)temp;
                        if (!dependencyObject.CheckAccess())
                        {
                            dependencyObject.Dispatcher.BeginInvoke(new System.Action(() => { this.SetResources(); }));
                        }
                        else
                        {
                            this.SetValue(BackgroundProperty, temp);                            
                        }
                    }                   
                }
          }                  
    }

现在这只是设置背景属性。我相信这对于样式也应该起作用。所以你可以这样做

this.SetValue(StyleProperty, temp)

花了一点时间才搞清楚。但是一旦我让它工作起来,我就感到非常兴奋。看起来我们的资源正在使用的依赖对象遇到了线程问题,因此第一次加载时它是在正确的线程上,但是在某个地方另一个线程被触发了。还没有找出这个问题的解决方法。如果有人有更好的解决方案,我很乐意看看。

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