5

我在开发 WPF 应用程序时遇到了一个问题。该应用程序基于棱镜。应用程序使用 prism 引导程序启动,在加载任何窗口之前,应用程序在不同的线程 (STA) 上打开一个模式对话框,然后加载一堆东西(服务等)。该对话框在此期间打开并允许通知用户应用程序启动过程的进度(使用事件聚合器传递更新)。加载完成后,引导程序关闭对话框并打开主应用程序窗口。 到目前为止,一切都很好... 然后在关闭应用程序时,同样的事情正在发生。关闭主窗口,打开一个对话框(再次在新的 STA 线程上),以允许通知。但是现在,当点击 ShowDialog 调用(发生在新的 STA 线程内)时,会引发异常:“不能使用属于与其父 Freezable 不同的线程的 DependencyObject”。经过长时间的调试,我发现异常的原因是窗口的背景,它是从应用程序级别的合并字典中获取的画笔/图像(在 wpf UI 线程上实例化)。如果在没有 ResouceDictionary 的情况下加载图像 - 一切顺利。


总结: 只有在使用 resourceDictionary 并且仅在第二次调用新的 STA 线程时才会观察到异常,该线程反过来加载 DialogBox 并在调用 ShowDialog 时准确地引发异常 如果您只有一个对话框(例如,在启动时间并且仅在关机过程中出现对话框),则不会发生异常。


我的问题是:这是什么原因?在这种情况下,这个例外到底意味着什么?(我知道通常会有某种 UI 线程更新来自其他线程,但是我不明白为什么这只发生在 dialgo+thread 的第二个实例上)。

谢谢 :)

4

2 回答 2

2

正如您正确提到的,您的背景对象是在主 UI 线程上创建的。您的背景实际上是一个 Brush 对象,而 Brush 是一个 DependencyObject。

创建 DependencyObject 时,它“依赖”于创建它的 STA 线程。所以像其他依赖对象一样,它只能在自己的线程上使用。这意味着 STA 和对旧 COM 对象模型的兼容性。

因此,当您尝试在另一个 STA 线程上使用它时,您会得到一个适当的异常。

PS 我对被定义为资源的图像有同样的问题。

于 2014-10-13T15:25:23.430 回答
1

我有一个类似的问题。我不确定你是如何实现背景的。我可以试着解释一下我的情况,也许你能从中得到一些东西。我创建了自己的基础 Window,我们称它为 MyWindow,它继承自 Window。IE。

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"); 

现在你有了依赖对象!这就是导致我们与可冻结父级的线程问题的原因。现在我们在这个对象上调用调度程序。我最终得到了这样的东西。这对我有用,但我可能会尝试清理一下。

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)

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

于 2012-10-20T19:59:59.197 回答