If you use SOS’s !Threads command during debugging a lot,  you should be familiar with such output:


0:003> !threads
PDB symbol for mscorwks.dll not loaded
Loaded Son of Strike data table version 5 from "C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorwks.dll"
ThreadCount: 12
UnstartedThread: 5
BackgroundThread: 1
PendingThread: 0
DeadThread: 5
                                  PreEmptive   GC Alloc                     Lock    
        ID  ThreadOBJ       State     GC       Context           Domain     Count APT Exception
  0  0xb74 0x0014f230        0x20 Enabled  0x00000000:0x00000000 x00149aa8     1 Ukn
  2  0xb58 0x00157cf8      0x1220 Enabled  0x00000000:0x00000000 x00149aa8     0 Ukn (Finalizer)
XXX      0 0x001665f0      0x1820 Enabled  0x00000000:0x00000000 x00149aa8     0 Ukn
XXX      0 0x0016d348      0x1400 Enabled  0x00000000:0x00000000 x00149aa8     0 Ukn
XXX      0 0x0016d510      0x1820 Enabled  0x00000000:0x00000000 x00149aa8     0 Ukn
XXX      0 0x0016d9d0      0x1400 Enabled  0x00000000:0x00000000 x00149aa8     0 Ukn
XXX      0 0x0016db98      0x1820 Enabled  0x00000000:0x00000000 x00149aa8     0 Ukn
XXX      0 0x0016e248      0x1400 Enabled  0x00000000:0x00000000 x00149aa8     0 Ukn
XXX      0 0x0016e410      0x1820 Enabled  0x00000000:0x00000000 x00149aa8     0 Ukn
XXX      0 0x0016e740      0x1400 Enabled  0x00000000:0x00000000 x00149aa8     0 Ukn
XXX      0 0x0016e908      0x1820 Enabled  0x00000000:0x00000000 x00149aa8     0 Ukn
XXX      0 0x0016ec98      0x1400 Enabled  0x00000000:0x00000000 x00149aa8     0 Ukn


 
Have you ever wondered what exactly are in the list? Why the number of threads listed here doesn’t match the number of "real" threads in the process (in the example above, I only have 4 threads in the process, but !Threads shows 12)? Why some "real" threads have entries here, some don't? Maybe they are managed System.Threading.Thread objects (but the number in the list might not match number of Thread objects either)? Answers to those questions are tied to how CLR implements threads and manages thread information.
 
CLR provides classes in System.Threading namespace as threading APIs. As you know, threading is implemented by utilizing native threads in underlying OS (Windows, in CLR case). CLR just piggybacks managed threads to native OS threads. Users use BCL System.Threading.Thread objects (I’ll call it as C# Thread below) to control managed threads just as thread HANDLE is used to control to windows native threads. If you ever checked contents of a C# Thread object, you will find it is quite small (here I use SOS !DumpObj command, you could also see it using Visual Studio or other managed debuggers):
 


0:003> !DumpObj 0x00c03248
Name: System.Threading.Thread
MethodTable 0x79bb8384
EEClass 0x79bb85b0
Size 60(0x3c) bytes
GC Generation: 0
mdToken: 0x020000eb  (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)
FieldDesc*: 0x79bb8614
        MT      Field     Offset                 Type       Attr      Value Name
0x79bb8384 0x4000330      0x4                CLASS   instance 0x00000000 m_Context
0x79bb8384 0x4000331      0x8                CLASS   instance 0x00000000 m_LogicalCallContext
0x79bb8384 0x4000332      0xc                CLASS   instance 0x00000000 m_IllogicalCallContext
0x79bb8384 0x4000333     0x10                CLASS   instance 0x00000000 m_Name
0x79bb8384 0x4000334     0x14                CLASS   instance 0x00000000 m_ExceptionStateInfo
0x79bb8384 0x4000335     0x18                CLASS   instance 0x00000000 m_Delegate
0x79bb8384 0x4000336     0x1c                CLASS   instance 0x00000000 m_PrincipalSlot
0x79bb8384 0x4000337     0x20                CLASS   instance 0x00000000 m_ThreadStatics
0x79bb8384 0x4000338     0x24                CLASS   instance 0x00000000 m_ThreadStaticsBits
0x79bb8384 0x4000339     0x28                CLASS   instance 0x00000000 m_CurrentCulture
0x79bb8384 0x400033a     0x2c                CLASS   instance 0x00000000 m_CurrentUICulture
0x79bb8384 0x400033b     0x30         System.Int32   instance 2 m_Priority
0x79bb8384 0x400033c     0x34         System.Int32   instance 1498008 DONT_USE_InternalThread
0x79bb8384 0x400033d        0                CLASS     shared   static m_LocalDataStoreMgr
 


CLR actually needs much more information about a thread than fields of the C# Thread class, and such information is needed in CLR's unmanaged part where it's not easy to access managed objects. So inside Execution Engine (so far Execution Engine is still written in unmanaged code), there is an unmanaged C++ class also called Thread (let me call  it C++ Thread) to keep all information for an OS native thread (OS thread). In Rotor, this class is defined in vm\threads.h.
 
CLR needs to create a C++ Thread object for every OS thread EE knows of, that is, every thread which ever ran managed code. Such threads could either be (a) created explicitly by users using C# Thread.Start, (b) an unmanaged OS thread who has ever visited managed world, e.g, through interop, or (c) a special OS thread in CLR which might run managed code. For case a, when a user creates a C# Thread object, CLR will create a C++ Thread object, link it to the C# Thread object, and mark it as unstarted (Rotor: SetupUnstartedThread in vm\threads.cpp). Once Start method is called on the C# Thread object, CLR will create an OS thread, in the OS thread’s ThreadPproc (Rotor: ThreadNative::KickOffThread in vm\ComSynchronizable.cpp), CLR will save the C++ Thread object’s address to the OS thread's TLS (Thread Local Storage) and mark it to be started (Rotor: ThreadStore::TransferStartedThread in vm\threads.cpp); For case b, at entry point for an unmanaged OS thread to managed world, CLR will create a C++ Thread object and also use TLS to associate it with the OS thread (Rotor: SetupThread in vm\threads.cpp); Case c is similar to case b, except CLR might set up C++ Thread earlier.
 
In any case, the C++ Thread object is the primary place for CLR to store information regarding to a managed thread. When CLR needs to access the information, it will just fetch the object from the OS thread's TLS. In that sense, the C# Thread is more like a managed proxy for the C++ Thread. In fact, for case b and c, there will be no C# Thread objects created unless users call System.Threading.Thread.CurrentThread. C++ Thread tracks its corresponding C# Thread using a GCHandle (m_ExposedObject field in C++ Thread object); C# Thread tracks its C++ Thread using a native pointer(DONT_USE_InternalThread field in C# Thread). You could verify this circular reference in debugger if you have symbols for mscorwks.dll:
 


0:003> !do 0x00c03248
Name: System.Threading.Thread

        MT      Field     Offset                 Type       Attr      Value

0x79bb8384 0x400033c     0x34         System.Int32   instance 1498008 DONT_USE_InternalThread

 
0:013> dt mscorwks!Thread 0n1498008

+0x0c0 m_ExposedObject  : 0x00a710e4

 
0:013> dp 0x00a710e4 l1
00a710e4  00c03248


 
CLR uses a data structure called Thread Store (Rotor: ThreadStore in vm\threads.h) to keep all C++ Threads. What you see in output of !Threads command is actually list of C++ Threads in Thread Store. The “ThreadOBJ” field is address of a C++ Thread object. Other fields are important information about the C++ Thread, including OS thread ID for the corresponding OS thread. Here you could see not every live OS thread has a C++ Thread, which could be explained by the fact that CLR only creates a C++ Thread for an OS thread which ever runs managed code. Meanwhile you may also find some C++ Threads don't have an OS thread associated with them (the ID fields are “XXX”). They are either (a)unstarted, (b) failed to started, (c)used to represent an live OS thread which is now dead. For case (a), CLR will wait until C# Thread.Start is called and create an OS thread for it then; for (b) and (c), the C++ Threads object will be deleted some time later.

 

This posting is provided "AS IS" with no warranties, and confers no rights.