(Post 30/10/2007) Apartment threading is an
automatic thread-safety regime, closely allied to COM – Microsoft's legacy
Component Object Model. While .NET largely breaks free of legacy threading
models, there are times when it still crops up because of the need to
interoperate with older APIs. Apartment threading is most relevant to
Windows Forms, because much of Windows Forms uses or wraps the long-standing
Win32 API – complete with its apartment heritage.
An apartment is a logical "container" for threads.
Apartments come in two sizes – "single" and "multi".
A single-threaded apartment contains just one thread; multi-threaded apartments
can contain any number of threads. The single-threaded model is the more
common and interoperable of the two.
As well as containing threads, apartments contain objects.
When an object is created within an apartment, it stays there all its
life, forever house-bound along with the resident thread(s). This is similar
to an object being contained within a .NET synchronization context, except
that a synchronization context does not own or contain threads. Any thread
can call upon an object in any synchronization context – subject to waiting
for the exclusive lock. But objects contained within an apartment can
only be called upon by a thread within the apartment.
Imagine a library, where each book represents an object.
Borrowing is not permitted – books created in the library stay there for
life. Furthermore, let's use a person to represent a thread.
A synchronization context library allows any person to
enter, as long as only one person enters at a time. Any more, and a queue
forms outside the library.
An apartment library has resident staff – a single librarian
for a single-threaded library, and whole team for a multi-threaded library.
No-one is allowed in other than members of staff – a patron wanting to
perform research must signal a librarian, then ask the librarian to do
the job! Signaling the librarian is called marshalling – the patron marshals
the method call over to a member of staff (or, the member of staff!) Marshalling
is automatic, and is implemented at the librarian-end via a message pump
– in Windows Forms, this is the mechanism that constantly checks for keyboard
and mouse events from the operating system. If messages arrive too quickly
to be processed, they enter a message queue, so they can be processed
in the order they arrive.
Specifying an Apartment Model
A .NET thread is automatically assigned an apartment
upon entering apartment-savvy Win32 or legacy COM code. By default, it
will be allocated a multi-threaded apartment, unless one requests a single-threaded
apartment as follows:
Thread t = new Thread (...);
t.SetApartmentState (ApartmentState.STA);
One can also request that the main thread join a single-threaded
apartment using the STAThread attribute on the main method:
class Program {
[STAThread]
static void Main() {
...
Apartments have no effect while executing pure .NET code.
In other words, two threads with an apartment state of STA can simultaneously
call the same method on the same object, and no automatic marshalling
or locking will take place. Only when execution hits unmanaged code can
they kick in.
The types in the System.Windows.Forms namespace extensively
call Win32 code designed to work in a single-threaded apartment. For this
reason, a Windows Forms program should have have the [STAThread] attribute
on its main method, otherwise one of two things will occur upon reaching
Win32 UI code:
- it will marshal over to a single-threaded apartment
- it will crash
Control.Invoke
In a multi-threaded Windows Forms application, it's illegal
to call a method or property on a control from any thread other than the
one that created it. All cross-thread calls must be explicitly marshalled
to the thread that created the control (usually the main thread), using
the Control.Invoke or Control.BeginInvoke method. One cannot rely on automatic
marshalling because it takes place too late – only when execution gets
well into unmanaged code, by which time plenty of internal .NET code may
already have run on the "wrong" thread – code which is not thread-safe.
An excellent solution to managing worker threads in Windows
Forms applications is to use BackgroundWorker. This class wraps worker
threads that need to report progress and completion, and automatically
calls Control.Invoke as required.
(Sưu tầm) |