我对此非常困惑,它开始让我质疑我对wpf资源系统的整体理解。
我有一个多窗口应用程序,其中每个窗口派生的对象运行在一个单独的线程上,具有单独的调度程序。
Thread t = new Thread(() => {
Window1 win = new Window1();
win.Show();
System.Windows.Threading.Dispatcher.Run();
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
我有一个dictionary1.xaml资源字典,其中有一个命名样式对象(它只是将background属性设置为红色,并以文本框为目标)。在app.xaml中,我通过resourcedictionary.mergeddictionaries集合引用dictionary1.xaml。在其他窗口的xaml中,我有一个文本框控件中样式键的staticresource,它可以工作。
我可以打开多个窗口,但不应该出现跨线程错误吗?在一个窗口类的构造函数中,我这样做了:
Style s = (Style)TryFindResource("TestKey");
Console.WriteLine(((Setter)s.Setters[0]).Property.Name); // no problem
s.Dispatcher == this.Dispatcher // false
由于样式对象是从DispatcherObject派生的,这不意味着它只能由拥有它的线程访问吗?如果在ResourceDictionary中定义了一个对象,这不意味着默认情况下,它是一个静态实例吗?这怎么能奏效呢?为什么我没有得到一个交叉线程错误?
(我错误地报告了一个问题,因为我删除了一个关于由其他原因引起的跨线程错误的问题)
我对此非常困惑-我以为只有冻结的可释放对象可以跨线程共享。为什么允许我访问其他线程上的DispatcherObject?
最佳答案
所以我终于有了答案——我仔细研究了很多.NET框架代码,得出了以下结论:
当资源字典是应用程序级字典、主题字典或只读字典时,存储在资源字典中的所有项都将被“密封”
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();
}
}
“sealing”一个对象本质上是不可变的,通过isealable字典实现——事实上freezable通过调用freeze()实现了它的密封行为!样式也实现了它,它的实现防止了setter或trigger集合被修改。ISealable是由许多类实现的!
public abstract class Freezable : DependencyObject, ISealable
public class Style : DispatcherObject, INameScope, IAddChild, ISealable, IHaveResources, IQueryAmbient
更重要的是,到目前为止,我看到的每个wpf类中seal()的每个实现都调用dispatcheroobject.detachFromDispatcher(),它将Dispatcher设置为空!(freeze()内部也调用此函数)
“sealing”似乎实现了常被宣传为freezable独有的不变性行为,但是实现isealable的对象将表现出相同的行为,并且是线程安全的-freezable有其他区别于它的行为(例如子属性通知更改)
因此,总结一下,“密封”一个对象实际上使它能够跨线程共享,因为如果它存在于应用程序级或主题资源字典或只读资源字典中,则每个对象都会自动与其调度程序分离
若要验证此结论,请在ResourceDictionary上进行反射,并查看设置ResourceDictionary逻辑父级的addOwner()方法(例如FrameworkElement、FrameworkContentElement或应用程序)
这就是为什么笔刷和样式对象可以从其他线程访问的原因,因为资源字典被合并到应用程序中,因此所有资源都被自动密封-毫不奇怪地将这些资源放在窗口级别将导致常见的跨线程异常。
我想您可以概括为,isealable使wpf对象能够跨线程和只读地共享,尽管从技术上与Dispatcher分离不是协议的要求(就像DispatcherObject不需要在每个属性中进行VerifyAccess()调用一样),因为从技术上讲,每个对象都要实现自己的seal()行为,而Isealable和Dispatcher之间没有直接的关联