We know that it is not possible to execute code that manipulates the properties of any UI element from any thread other than the thread the element was instantiated on... My question is: Why?
I remember that when we used COM user interface elements, (in COM/Visual Basic 6.0 days), that all UI elements were created using COM classes and co-classes that stored their resources using a memory model referred to as Thread-Local-Storage (TLS), but as I recall, this was required because of something related to the way COM components were constructed, and should not be relevant to .NET UI elements. What's the underlying reason why this restriction still exists?
Is it because the underlying Operating System still uses COM-based Win32 API classe开发者_运维知识库s for all UI elements, even the ones manipulated in a managed .NET application?
AFAIK, it's more basic than even COM. It goes right down to the good ol' Windows API. I believe that windows in Windows are expected to be owned by a thread, period. Each thread has its own message pump, dispatching messages to the windows it owns. It's a pretty fundamental construct of Windows -- maybe a bit archaic these days, but fundamental.
My sense of it is that this thread affinity helps for interoperability when you need to integrate WPF into Windows Forms applications, or if you need to monkey with a Windows object somewhere else in your application using a HWND that you got somewhere... It probably also is what allows older versions of Windows (XP) to host WPF applications without any major architectural changes to the OS itself.
It sounds like you're referring to WPF rather than generic Windows API programming. I'm no expert on WPF internals, but here are a few items on why keeping UI manipulations in one UI thread are a good idea:
- Avoid Deadlocks. When you have multiple threads running around, all shared resources have to be protected by some sort of lock. When there are multiple locks, there is a high risk of getting stuck in a deadlock - thread A owns lock 1 but is waiting on lock 2, thread B owns lock 2 but is waiting for lock 1. This can be avoided by strict order of lock acquisition, but human nature falls short.
- Reduce the need for costly locks. If you can require all UI ops to execute on the UI thread, you can eliminate a lot of locks required to protect internal data.
- Reduce the risk of creating window handles owned by threads that don't have message loops.
That last item is Win API related. When a window handle is created, it is bound to the thread that issued the CreateWindow call. Messages sent to that window handle will be placed in the message queue associated with that thread. If that thread does not process window messages, the window will be non-responsive - UI frozen. If another thread tries to SendMessage to that window, that thread will also be frozen waiting for the synchronous call to complete. This snowballs pretty quickly into ugliness all over. This is why it's important to make sure that you only create window handles on threads that are prepared to process window messages.
Why is a window handle's message queue bound to a specific thread? I don't know, but I'm sure the answer is not trivial.
From http://msdn.microsoft.com/en-us/library/ms741870.aspx:
Historically, Windows allows UI elements to be accessed only by the thread that created them. This means that a background thread in charge of some long-running task cannot update a text box when it is finished. Windows does this to ensure the integrity of UI components. A list box could look strange if its contents were updated by a background thread during painting.
精彩评论