Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: mono-1-1-8
Fetching contributors…

Cannot retrieve contributors at this time

157 lines (125 sloc) 6.063 kb
Threading in Mono
=================
0. Terminology
--------------
"Main thread" - The initial OS-native thread that the
application started with.
"Helper thread" - A native thread created internally
by the runtime, such as the finalizer thread, or an
asynchronous delegate invocation thread. These
threads can run managed code.
"Primary CLR thread" - The native thread that called
the Main() method when executing an assembly.
"Secondary CLR thread" - A native thread created by a
program that instantiates a System.Threading.Thread object
and calls its Start() method.
1. Thread exit behaviour in the standalone mono runtime
-------------------------------------------------------
The correct behaviour of the runtime should be:
a) If Main() returns, the runtime should wait for all
foreground secondary CLR threads to finish. The
wording in the class documentation states: "Once all
foreground threads belonging to a process have
terminated, the common language runtime ends the
process by invoking Abort on any background threads
that are still alive." Testing seems to indicate that
the background thread can't cancel the Abort by
catching the ThreadAbortException and calling
ResetAbort here. Indeed, not even the finally block
seems to be executed.
b) if any of the primary CLR thread, a secondary CLR
thread or a helper thread calls
System.Environment.Exit(), the application should
terminate immediately without waiting for foreground
primary or secondary CLR threads to finish.
c) if the primary CLR thread throws an uncaught
exception, the application should terminate
immediately without waiting for secondary CLR threads
to finish. This might be implemented internally by
pretending that all the running secondary CLR threads
are background threads.
d) if a secondary CLR thread throws an uncaught
exception that thread should terminate and all other
threads should continue to execute.
e) if a helper thread throws an uncaught exception and
that thread happens to be the GC finalizer thread,
testing seems to indicate that the exception stack
trace is displayed as normal, and the exception is
then ignored (as though there is a try {} catch{}
around all finalizers that just prints the stack
trace.) Calling Abort() on the GC finalizer thread
also does not cause it to exit: it behaves as though
the ThreadAbortException is caught and ResetAbort is
called. Asynchronous delegate helper threads should
behave as secondary CLR threads, but uncaught
exceptions should be rethrown on the thread that calls
EndInvoke().
The difficulties happen with cases b) and c):
The current implementation of
System.Environment.Exit() calls exit(2) directly,
which is rather unfriendly: it prevents any runtime
cleanup, statistics gathering, etc. and is pretty
obnoxious to embedded code.
The current exception handling code calls ExitThread()
(emulated with pthread_exit() in the io-layer) if an
exception is not caught.
When called from the main thread, both POSIX
pthread_exit() and w32 ExitThread() block if there are
other threads still running (in the w32 case, if there
are other foreground threads still running; threads
can set as background.) If the main thread is also
the primary CLR thread, then the application will
block until all other threads (including helper
threads) terminate. Some helper threads will not
terminate until specifically told to by the runtime:
for example, the GC finalizer thread needs to run
until all of the primary and secondary CLR threads
have finished.
Also, if the main thread is also the primary CLR
thread, the runtime loses the opportunity to do any
cleaning up. Adding a special case to call exit(2)
instead of ExitThread() in the primary CLR thread
suffers from the same problems as
System.Environment.Exit() calling exit(2).
The simple solution is to run the primary CLR thread
in a new native thread, leaving the main thread free
for housekeeping duties. There still needs to be some
special handling for the case where the primary CLR
thread fails to catch an exception: the secondary CLR
threads then need to be terminated.
When the primary and secondary CLR threads have all
terminated, the helper threads can be killed off and
the runtime can clean itself up and exit.
2. Thread initialisation
------------------------
Threads have to undergo some initialisation before
managed code can be executed. A
System.Threading.Thread object must be created, and
the thread details need to be stored so that the
threads can be managed later. The JIT needs to record
the last managed frame stack pointer in a TLS slot,
and the current Thread object is also recorded.
New threads created by managed calls to
System.Threading.Thread methods will have all needed
initialisation performed. Threads created by the
runtime with calls to mono_thread_create() will too.
Existing threads can be passed to the runtime; these
must call mono_thread_attach() before any CLR code can
be executed on that thread.
3. Constraints on embedding the Mono runtime
--------------------------------------------
The discussion above concerning application behaviour
in the event of threads terminating, whether by
returning from the start function, throwing uncaught
exceptions or by calling System.Environment.Exit(),
only really applies to the standalone Mono runtime.
An embedding application should specify what behaviour
is required when, for example,
System.Environment.Exit() is called. The application
is also responsible for its own thread management, and
it should be prepared for any of the primary CLR
thread or secondary CLR threads to terminate at any
time. The application should also take into account
that the runtime will create helper threads as needed,
as this may cause pthread_exit() or ExitThread() to
block indefinitely, as noted above.
Jump to Line
Something went wrong with that request. Please try again.