我对此感到非常困惑,这让我开始质疑我对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属性设置为Red,并以TextBox为目标)。在我的App.xaml中,我通过ResourceDictionary.MergedDictionaries集合引用Dictionary1.xaml。在我的其他窗口的XAML中,我有一个指向textbox控件中Style键的StaticResource,它可以工作。
我可以打开多个窗口,但是我不应该得到交叉线程错误吗?在其中一个窗口类的构造函数中,我这样做了:
Style s = (Style)TryFindResource("TestKey");
Console.WriteLine(((Setter)s.Setters[0]).Property.Name); // no problem
s.Dispatcher == this.Dispatcher // false既然样式对象是从DispatcherObject派生的,这是否意味着只有拥有它的线程才能访问它?如果一个对象是在ResourceDictionary中定义的,这是否意味着在默认情况下,它是一个静态实例?这是如何工作的呢?为什么我没有得到一个跨线程的错误?
(我错误地报告了一个问题,因为我删除了一个由其他原因引起的交叉线程错误)
我对此感到非常困惑--我以为只有冻结的可冻结对象才能在线程间共享。为什么允许我访问其他线程上的DispatcherObject?
发布于 2012-11-26 12:49:05
所以我终于有了答案--我深入研究了大量的.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();
}
}“密封”对象本质上是不可变的,并且是通过Freezable字典实现的--实际上Freezable通过调用ISealable ()来实现它的密封行为!样式也实现了它,并且它的实现可以防止Setter或Triggers集合被修改。ISealable是由许多类实现的!
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之间没有直接的关联
发布于 2012-11-20 17:17:19
我对你的“问题”也很困惑。dispatcher对象没有什么神奇之处,线程亲和性应该在继承自DispatcherObject的类中编码:每个不应该兼容多线程的访问器都应该调用DispatcherObject提供的base.VerifyAccess()方法。下面是定义Style.Setters属性的getter的方式:
[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操作符没有被重载(尽管我没有发现任何重载...)。
这个主题非常有趣,请随时与我们联系!
发布于 2012-11-20 17:47:39
问得好,有两点我想提一下,
第一点是,每次你请求一个样式时,它都会给你一个新的样式对象,所以样式中的对象永远不会在多个控件之间共享,因为样式就像是包含样式信息的类的一部分。
第二点是,为什么dispatcher要抛出异常,因为dispatcher总是在单线程上运行。因此,当您更改控件的dispatcher时,线程亲和性规则使用dispatcher并在GUI线程上呈现内容。
这个信息有帮助吗?
https://stackoverflow.com/questions/13468103
复制相似问题