首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么Freezables在存储在Application.Resources中时已经冻结,并且有一个空的调度程序(与Style相同)?

为什么Freezables在存储在Application.Resources中时已经冻结,并且有一个空的调度程序(与Style相同)?
EN

Stack Overflow用户
提问于 2012-11-20 15:02:19
回答 3查看 987关注 0票数 6

我对此感到非常困惑,这让我开始质疑我对WPF资源系统的整体理解

我有一个多窗口应用程序,其中每个窗口派生的对象都运行在具有单独调度程序的单独线程上。

代码语言:javascript
运行
复制
Thread t = new Thread(() => {
    Window1 win = new Window1();
    win.Show();
    System.Windows.Threading.Dispatcher.Run();
});
t.SetApartmentState(ApartmentState.STA);
t.Start();

我有一个Dictionary1.xaml资源字典,里面有一个命名样式对象(它只是将Background属性设置为Red,并以TextBox为目标)。在我的App.xaml中,我通过ResourceDictionary.MergedDictionaries集合引用Dictionary1.xaml。在我的其他窗口的XAML中,我有一个指向textbox控件中Style键的StaticResource,它可以工作。

我可以打开多个窗口,但是我不应该得到交叉线程错误吗?在其中一个窗口类的构造函数中,我这样做了:

代码语言:javascript
运行
复制
Style s = (Style)TryFindResource("TestKey");
Console.WriteLine(((Setter)s.Setters[0]).Property.Name);    // no problem
s.Dispatcher == this.Dispatcher    // false

既然样式对象是从DispatcherObject派生的,这是否意味着只有拥有它的线程才能访问它?如果一个对象是在ResourceDictionary中定义的,这是否意味着在默认情况下,它是一个静态实例?这是如何工作的呢?为什么我没有得到一个跨线程的错误?

(我错误地报告了一个问题,因为我删除了一个由其他原因引起的交叉线程错误)

我对此感到非常困惑--我以为只有冻结的可冻结对象才能在线程间共享。为什么允许我访问其他线程上的DispatcherObject?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2012-11-26 12:49:05

所以我终于有了答案--我深入研究了大量的.NET框架代码,并得出了以下结论:

当资源字典是应用程序级字典、主题字典或只读字典时,资源字典中存储的所有项都将被“密封”。

代码语言:javascript
运行
复制
if (this.IsThemeDictionary || this._ownerApps != null || this.IsReadOnly)
{
    StyleHelper.SealIfSealable(value);
}

... 

internal static void SealIfSealable(object value)
{
ISealable sealable = value as ISealable;
if (sealable != null && !sealable.IsSealed && sealable.CanSeal)
{
    sealable.Seal();
}
}

“密封”对象本质上是不可变的,并且是通过Freezable字典实现的--实际上Freezable通过调用ISealable ()来实现它的密封行为!样式也实现了它,并且它的实现可以防止Setter或Triggers集合被修改。ISealable是由许多类实现的!

代码语言:javascript
运行
复制
public abstract class Freezable : DependencyObject, ISealable
public class Style : DispatcherObject, INameScope, IAddChild, ISealable, IHaveResources, IQueryAmbient

更重要的是,(到目前为止)我所见过的每个WPF类中的Seal()实现都会调用DispatcherObject.DetachFromDispatcher(),这会将dispatcher设置为空!(Freeze()内部也会调用这个函数)

“密封”似乎实现了通常被宣传为Freezable独有的不可变性行为,但实现ISealable的对象将表现出相同的行为,并且是线程安全的-Freezable有额外的行为来区分它(例如子属性通知更改)

综上所述,如果对象存在于应用程序级或主题资源字典或只读资源字典中,则由于每个对象都自动与其分派器分离,因此“密封”对象实际上使其能够在线程之间共享

要验证这一结论,请仔细考虑ResourceDictionary并查看AddOwner()方法,该方法设置ResourceDictionary的逻辑父项(例如FrameworkElement、FrameworkContentElement或应用程序)

这就是为什么笔刷和样式对象可以从其他线程访问,因为资源字典被合并到Application.Resources中,因此都是自动密封的-毫不奇怪,将这些资源放在窗口级别将导致常见的跨线程异常。

我猜您可以概括出,ISealable是使WPF对象能够跨线程共享和只读的原因,尽管从技术上讲,与Dispatcher分离并不是协议的要求(就像DispatcherObjects不需要在每个属性中进行VerifyAccess()调用一样),因为从技术上讲,每个对象都要实现自己的Seal()行为,并且ISealable和Dispatcher之间没有直接的关联

票数 4
EN

Stack Overflow用户

发布于 2012-11-20 17:17:19

我对你的“问题”也很困惑。dispatcher对象没有什么神奇之处,线程亲和性应该在继承自DispatcherObject的类中编码:每个不应该兼容多线程的访问器都应该调用DispatcherObject提供的base.VerifyAccess()方法。下面是定义Style.Setters属性的getter的方式:

代码语言:javascript
运行
复制
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public SetterBaseCollection Setters
{
    get
    {
        base.VerifyAccess();
        if (this._setters == null)
        {
            this._setters = new SetterBaseCollection();
            if (this._sealed)
            {
                this._setters.Seal();
            }
        }
        return this._setters;
    }
}

因此,在您的情况下,确实应该抛出一个异常...

也许您可以尝试调用s.CheckAccess()方法。您还可以尝试使用dispatcher比较Object.ReferenceEquals(A,B)方法来确保equal操作符没有被重载(尽管我没有发现任何重载...)。

这个主题非常有趣,请随时与我们联系!

票数 2
EN

Stack Overflow用户

发布于 2012-11-20 17:47:39

问得好,有两点我想提一下,

第一点是,每次你请求一个样式时,它都会给你一个新的样式对象,所以样式中的对象永远不会在多个控件之间共享,因为样式就像是包含样式信息的类的一部分。

第二点是,为什么dispatcher要抛出异常,因为dispatcher总是在单线程上运行。因此,当您更改控件的dispatcher时,线程亲和性规则使用dispatcher并在GUI线程上呈现内容。

这个信息有帮助吗?

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/13468103

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档