What does SynchronizationContext do?
Simply put, SynchronizationContext
represents a location "where" code might be executed. Delegates that are passed to its Send
or Post
method will then be invoked in that location. (Post
is the non-blocking / asynchronous version of Send
.)
Every thread can have a SynchronizationContext
instance associated with it. The running thread can be associated with a synchronization context by calling the static SynchronizationContext.SetSynchronizationContext
method, and the current context of the running thread can be queried via the SynchronizationContext.Current
property.
Despite what I just wrote (each thread having an associated synchronization context), a SynchronizationContext
does not necessarily represent a specific thread; it can also forward invocation of the delegates passed to it to any of several threads (e.g. to a ThreadPool
worker thread), or (at least in theory) to a specific CPU core, or even to another network host. Where your delegates end up running is dependent on the type of SynchronizationContext
used.
Windows Forms will install a WindowsFormsSynchronizationContext
on the thread on which the first form is created. (This thread is commonly called "the UI thread".) This type of synchronization context invokes the delegates passed to it on exactly that thread. This is very useful since Windows Forms, like many other UI frameworks, only permits manipulation of controls on the same thread on which they were created.
What if I just write myTextBox.Text = text;
in the method, what's the difference?
The code that you've passed to ThreadPool.QueueUserWorkItem
will be run on a thread pool worker thread. That is, it will not execute on the thread on which your myTextBox
was created, so Windows Forms will sooner or later (especially in Release builds) throw an exception, telling you that you may not access myTextBox
from across another thread.
This is why you have to somehow "switch back" from the worker thread to the "UI thread" (where myTextBox
was created) before that particular assignment. This is done as follows:
While you are still on the UI thread, capture Windows Forms' SynchronizationContext
there, and store a reference to it in a variable (originalContext
) for later use. You must query SynchronizationContext.Current
at this point; if you queried it inside the code passed to ThreadPool.QueueUserWorkItem
, you might get whatever synchronization context is associated with the thread pool's worker thread. Once you have stored a reference to Windows Forms' context, you can use it anywhere and at any time to "send" code to the UI thread.
Whenever you need to manipulate a UI element (but are not, or might not be, on the UI thread anymore), access Windows Forms' synchronization context via originalContext
, and hand off the code that will manipulate the UI to either Send
or Post
.
Final remarks and hints:
What synchronization contexts won't do for you is telling you which code must run in a specific location / context, and which code can just be executed normally, without passing it to a SynchronizationContext
. In order to decide that, you must know the rules and requirements of the framework you're programming against — Windows Forms in this case.
So remember this simple rule for Windows Forms: DO NOT access controls or forms from a thread other than the one that created them. If you must do this, use the SynchronizationContext
mechanism as described above, or Control.BeginInvoke
(which is a Windows Forms-specific way of doing exactly the same thing).
If you're programming against .NET 4.5 or later, you can make your life much easier by converting your code that explicitly uses SynchronizationContext
, ThreadPool.QueueUserWorkItem
, control.BeginInvoke
, etc. over to the new async
/ await
keywords and the Task Parallel Library (TPL), i.e. the API surrounding the Task
and Task<TResult>
classes. These will, to a very high degree, take care of capturing the UI thread's synchronization context, starting an asynchronous operation, then getting back onto the UI thread so you can process the operation's result.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…