I have a Dictionary1.xaml resource dictionary with a named Style object inside it (it just sets the Background property to Red and is targetted at a TextBox). In my App.xaml I reference Dictionary1.xaml via the ResourceDictionary.MergedDictionaries collection. In the XAML of my other windows I have a StaticResource to the style key in a textbox control, which works.

I'm able to open multiple windows but shouldn't I be getting cross-threading errors? In the constructor of one of the window classes I did this:

Since a Style object is derived from DispatcherObject, doesn't that mean it's only accessible to the thread that owns it? And if an object is defined in a ResourceDictionary, doesn't that mean that by default, it's a static instance? How is this even able to work? Why aren't I getting a cross-threading error?

(I erroneously reported a question I since deleted about a cross threading error that was caused by something else)

I'm very confused by this - I thought only frozen Freezable objects were shareable across threads. Why am I allowed to access a DispatcherObject on other threads?

3 Answers
3

I am also very confused with your "issue". There is no magic with dispatcher object, the thread affinity should be coded in the class inheriting from DispatcherObject: Each accessor that shouldn't be multi threading compliant should call the base.VerifyAccess() method provided by DispatcherObject. And here is the way the getter of Style.Setters property is defined:

Maybe you could try to call the s.CheckAccess() method.
You could also try for dispatcher comparison Object.ReferenceEquals(A,B) method to ensure the fact the equal operator has not been overloaded (although I didn't find any overloading...).

Yep, I tried CheckAccess and it returned true while VerifyAccess did not throw an exception. ReferenceEquals returns false predictably too. Thanks for reminding me that DispatcherObject isn't a magical object too, and that the thread affinity is enforced by manual calls to VerifyAccess() - great point / reminder! Very confused still
–
blue18hutthuttNov 20 '12 at 14:02

1

As a further test, I just created a <SolidColorBrush x:Key="TestBrush" Color="Blue" /> in the resource dictionary. This should create an unfrozen new SolidColorBrush, which by definition, should NOT be accessible on another thread and yet ...it is AND by the time I retrieve it from another window - it is already frozen! My understanding of how I thought WPF worked just plummeted ...
–
blue18hutthuttNov 20 '12 at 15:36

1

Here is the code for Dispatcher.CheckAccess() method: "return this.Thread == Thread.CurrentThread;" "this.Thread" is naturally the thread instance stored at object instanciation. Could you confirm this test also returns true for you????
–
Charles HETIERNov 21 '12 at 17:36

The plot thickens! When I fetch the Style and the Brush out of the App.xaml resource dictionary, they both have a NULL Dispatcher! Whoa! So now the question is, how is that possible? If I have the Style and the Brush in a local resources collection in the Window, then the Dispatcher properties are set (as you would expect) and the brush is unfrozen. If they are fetched from the App.xaml's Resources then the dispatcher is null AND the brush is already frozen!!! There must be something about the way App.xaml stores objects in the resource dictionary
–
blue18hutthuttNov 22 '12 at 0:00

I have an answer to the question now!
–
blue18hutthuttNov 26 '12 at 4:49

First point is, every time you request a style it will give you a new object of style so never the objects in a style are shared between multiple controls because style is just like a part of class that contains style info.

Second point is, why dispatcher should throw an exception as your dispatcher is always running on single thread. So when you the change the dispatcher of a control the thread affinity rule use the dispatcher and render the things on GUI thread.

This is not correct - any object that's instantiated in a Resources collection is by default, shared. I verified it just then by adding the Style to a global cache object at the start then doing a ReferenceEquals comparison from the constructor of one of the child window classes and they are the same (x:Shared="false" to have them separate instances). I think an exception should be thrown because each window runs on a separate thread and I'm also starting a separate dispatcher on each thread too, thus the Style object should be inaccessible without marshalling
–
blue18hutthuttNov 20 '12 at 13:45

it depends on the use but style are always like this.. you can use one style in many resources. and if one style effecting change does not mean it will change to all places where ever it is applied. if this is not your case then it might be different.
–
D JNov 20 '12 at 14:17

Well that all depends on where the Style is defined - if it's stored in Window.Resources for each of my custom window classes, then it would be a separate Style object for each window instance, however if they are defined in App.xaml they will be the same Style object
–
blue18hutthuttNov 20 '12 at 15:43

"Sealing" an object essentially renders it immutable and is implemented via the ISealable dictionary - in fact Freezable implements it's sealing behavior by calling Freeze()! Styles implement it too and its implementation prevents the Setters or Triggers collections from being modified. ISealable is implemented by many classes!

More importantly, every implementation of Seal() in every WPF class I've seen (so far) calls DispatcherObject.DetachFromDispatcher() which sets the dispatcher to null! (Freeze() internally calls this too)

"Sealing" appears to implement the immutability behavior that is often advertised as being exclusive to Freezable, but objects implementing ISealable will exhibit the same behavior and be thread-safe - Freezables have additional behavior that distinguishes it (eg sub-property notification change)

So to conclude, "sealing" an object, in effect, enables it to be shared across threads due to each object being detached from its dispatcher automatically if it is present in an Application-level or theme resource dictionary or in a read-only resource dictionary

To verify this conclusion, reflect over ResourceDictionary and look at the AddOwner() method that sets the logical parent of the ResourceDictionary (eg a FrameworkElement, FrameworkContentElement or Application)

This is why the brushes and Style object were accessible from other threads, because the resource dictionaries were merged into the Application.Resources and were thus all automatically sealed - not surprisingly putting these resources at the Window-level will cause the usual cross-threading exception.

I guess you can generalize that ISealable is what enables WPF objects to be shareable across threads and read-only, although technically detaching from the Dispatcher is not a requirement of the protocol (just like DispatcherObjects are not required to make VerifyAccess() calls in every property) since it's technically up to every object to implement it's own Seal() behavior and there is no immediate correlation between ISealable and Dispatcher