我有一个多窗口应用程序,其中每个Window派生对象在一个单独的线程上运行,并带有单独的调度程序.
Thread t = new Thread(() => { Window1 win = new Window1(); win.Show(); System.Windows.Threading.Dispatcher.Run(); }); t.SetApartmentState(ApartmentState.STA); t.Start();
我有一个Dictionary1.xaml资源字典,里面有一个名为Style的对象(它只是将Background属性设置为Red,并以TextBox为目标).在我的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
由于Style对象是从DispatcherObject派生的,这是否意味着它只能由拥有它的线程访问?如果在ResourceDictionary中定义了一个对象,这是不是意味着默认情况下它是一个静态实例?这怎么可以工作?为什么我没有遇到跨线程错误?
(我错误地报告了一个问题,因为我删除了一个由其他东西引起的交叉线程错误)
我对此感到非常困惑 – 我认为只有冻结的Freezable对象才能跨线程共享.为什么我可以在其他线程上访问DispatcherObject?
解决方法
当资源字典是应用程序级别的字典,主题字典或只读时,存储在资源字典中的所有项目都将被“密封”
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(); } }
“密封”一个对象本质上使它成为不可变的,并通过ISealable字典实现 – 实际上Freezable通过调用Freeze()来实现它的密封行为!样式也实现它,它的实现可以防止修改Setters或Triggers集合. ISealable由许多类实现!
public abstract class Freezable : DependencyObject,ISealable public class Style : DispatcherObject,INameScope,IAddChild,ISealable,IHaveResources,IQueryAmbient
更重要的是,我见过的每个WPF类中的每个Seal()实现(到目前为止)调用DispatcherObject.DetachFromDispatcher(),它将调度程序设置为null! (Freeze()内部也调用它)
“Sealing”似乎实现了通常被宣传为Freezable独有的不可变性行为,但实现ISealable的对象将表现出相同的行为并且是线程安全的 – Freezables具有区分它的其他行为(例如,子属性通知更改)
总而言之,“密封”对象实际上使得它可以跨线程共享,因为如果每个对象都存在于应用程序级别或主题资源字典中或者只读资源中,则会自动从其调度程序中分离它们.字典
要验证此结论,请反映ResourceDictionary并查看设置ResourceDictionary的逻辑父级的AddOwner()方法(例如,FrameworkElement,FrameworkContentElement或Application)
这就是为什么可以从其他线程访问画笔和Style对象的原因,因为资源字典被合并到Application.Resources中,因此全部自动密封 – 毫不奇怪地将这些资源放在Window级别将导致通常的跨线程异常.
我想你可以推断,ISealable是使WPF对象可以跨线程和只读共享的原因,尽管从Dispatcher技术上分离不是协议的要求(就像DispatcherObjects不需要在每个调用中调用VerifyAccess()一样)因为它在技术上取决于每个对象实现它自己的Seal()行为,并且ISealable和Dispatcher之间没有直接的关联