Why is it that the thread that created the control is the one that can update it? Why didn't MS give people the ability to use locking and other thread synchronization techniques for reading and writing to properties on UI controls with multiple threads.
WPF requires that most of its objects be tied to the UI thread. This is known as thread affinity, meaning you can only use a WPF object on the thread on which it was created. Using it on other threads will cause a runtime exception to be thrown.
The UI thread queues work items inside an object called a Dispatcher. The Dispatcher selects work items on a priority basis and runs each one to completion. Every UI thread must have at least one Dispatcher, and each Dispatcher can execute work items in exactly one thread.
WPF supports a single-threaded apartment model that has the following rules: One thread runs in the entire application and owns all the WPF objects. WPF elements have thread affinity, in other words other threads can't interact with each other.
So thread affinity means that the thread, in this case the UI thread that instantiates an object is the only thread that can access its members. So for example, dependency object in WPF has thread affinity.
The short description per MSDN is
WPF’s threading model was kept in sync with the existing User32 threading model of single threaded execution with thread affinity. The primary reason for this was interoperability – systems like OLE 2.0, the clipboard, and Internet Explorer all require single thread affinity (STA) execution
The longer description is this:
Most objects in WPF derive from DispatcherObject, which provides the basic constructs for dealing with concurrency and threading. WPF is based on a messaging system implemented by the dispatcher. This works much like the familiar Win32 message pump; in fact, the WPF dispatcher uses User32 messages for performing cross thread calls.
There are really two core concepts to understand when discussing concurrency in WPF – the dispatcher and thread affinity.
During the design phase of WPF, the goal was to move to a single thread of execution, but a non-thread "affinitized" model. Thread affinity happens when a component uses the identity of the executing thread to store some type of state. The most common form of this is to use the thread local store (TLS) to store state. Thread affinity requires that each logical thread of execution be owned by only one physical thread in the operating system, which can become memory intensive. In the end, WPF’s threading model was kept in sync with the existing User32 threading model of single threaded execution with thread affinity. The primary reason for this was interoperability – systems like OLE 2.0, the clipboard, and Internet Explorer all require single thread affinity (STA) execution.
Given that you have objects with STA threading, you need a way to communicate between threads, and validate that you are on the correct thread. Herein lies the role of the dispatcher. The dispatcher is a basic message dispatching system, with multiple prioritized queues. Examples of messages include raw input notifications (mouse moved), framework functions (layout), or user commands (execute this method). By deriving from DispatcherObject, you create a CLR object that has STA behavior, and will be given a pointer to a dispatcher at creation time.
You can read the full article here
Personally I prefer WPF's single-threaded model over having to use locking and thread synchronization techniques. The Dispatcher can be used to pass messages to the main UI thread at different priority levels, which takes care of the majority of small background processes, and if you need any heavy processing then you can still create your own background thread for that.
WPF, like virtaully all UI toolkits, works by pumping a message loop. Since messages can come at any time and affect any control you would need a global lock. And to make it less error prone you probably want a function that would invoke a delegate under the lock. Perhaps something like this:
Dispatcher.Invoke(Delegate, Object())
The fact that this gets marshalled to the UI thread instead of acquiring a global lock is just an implementation detail.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With