<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.msdn.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Yun Jin's WebLog : Rotor code explanation</title><link>http://blogs.msdn.com/yunjin/archive/tags/Rotor+code+explanation/default.aspx</link><description>Tags: Rotor code explanation</description><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP1 (Build: 61025.2)</generator><item><title>Thread, System.Threading.Thread, and !Threads (III)</title><link>http://blogs.msdn.com/yunjin/archive/2005/08/30/457756.aspx</link><pubDate>Tue, 30 Aug 2005 19:55:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:457756</guid><dc:creator>yunjin</dc:creator><slash:comments>5</slash:comments><comments>http://blogs.msdn.com/yunjin/comments/457756.aspx</comments><wfw:commentRss>http://blogs.msdn.com/yunjin/commentrss.aspx?PostID=457756</wfw:commentRss><description>&lt;P&gt;I got email asking me to explain &lt;EM&gt;!Threads&lt;/EM&gt; output in details. I think this is a good question and a good topic for another installment to the series.&lt;/P&gt;
&lt;P&gt;Here is an example I'll use for this post:&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px"&gt;
&lt;P&gt;&lt;EM&gt;&lt;FONT size=2&gt;0:055&amp;gt; !threads&lt;BR&gt;ThreadCount: 202&lt;BR&gt;UnstartedThread: 95&lt;BR&gt;BackgroundThread: 1&lt;BR&gt;PendingThread: 0&lt;BR&gt;DeadThread: 47&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; PreEmptive&amp;nbsp;&amp;nbsp; GC Alloc&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Lock&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ID&amp;nbsp; ThreadOBJ&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; State&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; GC&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Context&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Domain&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Count APT Exception&lt;BR&gt;&amp;nbsp; 0&amp;nbsp; 0xed0 0x0014f260&amp;nbsp;&amp;nbsp; 0x2000020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1 Ukn&lt;BR&gt;&amp;nbsp; 1&amp;nbsp; 0xa3c 0x00157d28&amp;nbsp;&amp;nbsp; 0x2001220 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn (Finalizer)&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00166378&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp; 4 0x12cc 0x00166540&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 STA&lt;BR&gt;&amp;nbsp; 5 0x12dc 0x00166708&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp; 3&amp;nbsp; 0xe7c 0x00175b70&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00175d38&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00175f00&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x001760c8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00176290&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn System.InvalidOperationException&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00176458&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00176620&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x001767e8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp;12&amp;nbsp; 0x7e0 0x001769b0&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp;13 0x15e8 0x00178008&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp;14&amp;nbsp; 0x4d0 0x001781d0&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00178398&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00178560&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00178728&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x001788f0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00178ab8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00178c80&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00178e48&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp;21 0x14f0 0x00179010&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp;22 0x1708 0x001791d8&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp;23 0x11f8 0x001793a0&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp;24&amp;nbsp; 0x224 0x00179568&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00179730&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x001798f8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00179ac0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn System.InvalidOperationException&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00179c88&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn System.InvalidOperationException&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00179e50&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149aa0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;...&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&lt;STRONG&gt;First &lt;EM&gt;!Threads&lt;/EM&gt; gives some statistics about &lt;EM&gt;Thread Store&lt;/EM&gt;.&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;ThreadCount&lt;/EM&gt;: number of total &lt;EM&gt;C++ Thread&lt;/EM&gt; objects in &lt;EM&gt;Thread Store&lt;/EM&gt;.&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;UnstartedThread&lt;/EM&gt;: number of &lt;EM&gt;C++ Thread&lt;/EM&gt; objects marked as unstarted. Recall I mentioned in &lt;A href="http://http://blogs.msdn.com/yunjin/archive/2005/08/25/456355.aspx"&gt;previous blog&lt;/A&gt;, if a user creates a &lt;EM&gt;C# Thread&lt;/EM&gt; object, CLR will create an "unstarted" &lt;EM&gt;C++ Thread&lt;/EM&gt; object. When &lt;EM&gt;Thread.Start&lt;/EM&gt; is called on the C# object, CLR will create an &lt;EM&gt;OS thread&lt;/EM&gt; and remove "unstarted" flag from the &lt;EM&gt;C++ Thread&lt;/EM&gt; object.&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;BackgroundThread&lt;/EM&gt;: number of &lt;EM&gt;C++ Threads&lt;/EM&gt; (and the corresponding &lt;EM&gt;OS threads&lt;/EM&gt;) considered as background. Being background simply means CLR won't wait the thread for shutting down. Threads created explicitly by using &lt;EM&gt;System.Threading.Thread.Start&lt;/EM&gt; are by default foreground threads; whereas threads wandering into CLR from unmanaged world are by default background threads (Rotor: &lt;EM&gt;SetupThread&lt;/EM&gt; in&lt;EM&gt; vm/threads.cpp&lt;/EM&gt; calls &lt;EM&gt;SetBackground(TRUE)&lt;/EM&gt;). However, whether a thread is background could be changed by using&amp;nbsp;&lt;EM&gt;IsBackground&lt;/EM&gt; property in &lt;EM&gt;C# Thread&lt;/EM&gt; object.&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;PendingThreads&lt;/EM&gt;: If an &lt;EM&gt;OS thread&lt;/EM&gt; is created but its &lt;EM&gt;ThreadProc&lt;/EM&gt; hasn't be executed to the place to decrement unstarted counter in &lt;EM&gt;Thread Store&lt;/EM&gt;, the thread is considered&amp;nbsp;to be pending. Number of this type of threads should be quite low.&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;DeadThreads&lt;/EM&gt;: Number of &lt;EM&gt;C++ Thread&lt;/EM&gt; objects whose &lt;EM&gt;OS threads&lt;/EM&gt; are already dead but the &lt;EM&gt;C++ objects&lt;/EM&gt; themselves are not deleted yet.&lt;/P&gt;
&lt;P&gt;In Rotor, all the five numbers are actually stored in &lt;EM&gt;ThreadStore (vm/threads.h)&lt;/EM&gt; object as its fields.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Then it comes a table of all &lt;EM&gt;C++ Thread&lt;/EM&gt; objects in Thread Store. Let me explain each field.&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;The first column doesn't have a header. It is the &lt;EM&gt;OS thread&lt;/EM&gt; ID given by debugger just for debugging readability. Because the numbers only exist in debugger process, not the debuggee process, you may see the number being different when you look at a live session than when you debug a dump taken from the same live session. For a "dead"&amp;nbsp;or "unstarted" thread, this column is "XXX".&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;ID&lt;/EM&gt;: this is the thread ID assigned by OS, it remains consistent during debugger sessions, but OS could recycle it.&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;ThreadOBJ&lt;/EM&gt;: address of &lt;EM&gt;C++ Thread&lt;/EM&gt; object. You could see contents of the object by &lt;EM&gt;"dt mscorwks!Thread &amp;lt;address&amp;gt;"&lt;/EM&gt; if you have symbols for mscorwks.dll.&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;State&lt;/EM&gt;: one of the most important fields of the table. For Rotor, it is the &lt;EM&gt;C++ Thread's m_State&lt;/EM&gt; field.&amp;nbsp; It is combination of&amp;nbsp;bit masks&amp;nbsp;to indicate what the status the &lt;EM&gt;Thread&lt;/EM&gt; currently is. All possible states (bit masks)&amp;nbsp;are defined as enum &lt;EM&gt;ThreadState&lt;/EM&gt; in &lt;EM&gt;vm/Threads&lt;/EM&gt;. We already covered several states like &lt;EM&gt;TS_Background&lt;/EM&gt;, &lt;EM&gt;TS_Unstarted&lt;/EM&gt;, and &lt;EM&gt;TS_Dead&lt;/EM&gt;. More states include &lt;EM&gt;TS_AbortRequested&lt;/EM&gt; (this thread is requested to be aborted), &lt;EM&gt;TS_AbortInitiated&lt;/EM&gt; (abort process is already started for this thread), &lt;EM&gt;TS_GCSuspendPending&lt;/EM&gt; (GC is trying to suspend this thread), and etc.&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;Preemptive GC&lt;/EM&gt;: also very important. In Rotor, this is &lt;EM&gt;m_fPreemptiveGCDisabled&lt;/EM&gt; field of&lt;EM&gt; C++ Thread&lt;/EM&gt; class. It indicates what GC mode the thread is in: "enabled" in the table means the thread is in &lt;EM&gt;preemptive mode&lt;/EM&gt; where GC could preempt this thread at any time; "disabled" means the thread is in cooperative mode where GC has to wait the thread to give up its current work (the work is related to GC objects so it can't allow GC to move the objects around). When the thread is executing managed code (the current IP is in managed code), it is always in cooperative mode; when the thread is in &lt;EM&gt;Execution Engine&lt;/EM&gt; (unmanaged code), &lt;EM&gt;EE &lt;/EM&gt;code could choose to stay in either mode and could switch mode at any time; when a thread are outside of CLR (e.g, calling into native code using interop), it is always in preemptive mode.&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;GC Alloc context&lt;/EM&gt;: allocate context GC might use when it tries to allocate object for this thread. In Rotor, it is &lt;EM&gt;m_alloc_context&lt;/EM&gt; in &lt;EM&gt;C++ Thread&lt;/EM&gt; object. &lt;/P&gt;
&lt;P&gt;&lt;EM&gt;Domain&lt;/EM&gt;: which &lt;EM&gt;AppDomain&lt;/EM&gt; the thread is currently in (Rotor:&lt;EM&gt; m_pDomain&lt;/EM&gt; field of &lt;EM&gt;C++ Thread&lt;/EM&gt; class). You could use &lt;EM&gt;!DumpDomain&lt;/EM&gt; or "dt mscorwks!AppDomain" to dump details of the domain. A thread can only be in one domain at a time, but it could switch into different domains. Speical marks will be put on thread's stack to when it transit to another domain.&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;Lock count&lt;/EM&gt;: how many locks this thread has taken (Rotor: &lt;EM&gt;m_dwLockCount&lt;/EM&gt; field of &lt;EM&gt;C++ Thread&lt;/EM&gt; class). The locks it tracks include the managed monitors (taken by &lt;EM&gt;lock(obj)&lt;/EM&gt; in C#),&amp;nbsp;BCL's &lt;EM&gt;ReaderWriterLock&lt;/EM&gt;, and certain locks inside CLR's unmanaged code.&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;APT&lt;/EM&gt;: &lt;EM&gt;COM&lt;/EM&gt; apartment for the thread, whether the thread is in a single-threaded apartment (&lt;EM&gt;STA&lt;/EM&gt;), multithreaded apartment(&lt;EM&gt;MTA&lt;/EM&gt;) or unknown.&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;Exception&lt;/EM&gt;: the last managed exception thrown from this thread. It is saved in a GC handle in the &lt;EM&gt;C++ Thread&lt;/EM&gt; object (Rotor: &lt;EM&gt;m_LastThrownObjectHandle&lt;/EM&gt;).&lt;/P&gt;
&lt;P&gt;The last column also indicates which &lt;a href="http://blogs.msdn.com/yunjin/archive/2005/07/05/435726.aspx"&gt;special thread &lt;/A&gt;this thread is. However,&amp;nbsp;&lt;EM&gt;!Threads&lt;/EM&gt; only recognize&amp;nbsp;a limited type of special threads for this field, including &lt;EM&gt;Finalizer thread&lt;/EM&gt;, &lt;EM&gt;GC thread&lt;/EM&gt;, &lt;EM&gt;Threadpool Worker thread&lt;/EM&gt;, and &lt;EM&gt;Threadpool Completion Port thread&lt;/EM&gt;. And for special threads which doesn't have a &lt;EM&gt;C++ Thread &lt;/EM&gt;object (a special thread doesn't need to run managed code like debugger helper thread and server GC thread), they&amp;nbsp;can not be displayed here. In Whidbey, a "&lt;EM&gt;-special&lt;/EM&gt;" option is added to &lt;EM&gt;!Threads &lt;/EM&gt;command which will show all special threads in the process as a separate list. Here is a sample output:&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px"&gt;
&lt;P&gt;&lt;EM&gt;&lt;FONT size=2&gt;0:007&amp;gt; !threads -special&lt;BR&gt;ThreadCount: 4&lt;BR&gt;UnstartedThread: 0&lt;BR&gt;BackgroundThread: 3&lt;BR&gt;PendingThread: 0&lt;BR&gt;DeadThread: 0&lt;BR&gt;Hosted Runtime: no&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; PreEmptive&amp;nbsp;&amp;nbsp; GC Alloc&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Lock&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ID OSID ThreadOBJ&amp;nbsp;&amp;nbsp;&amp;nbsp; State&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; GC&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Context&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Domain&amp;nbsp;&amp;nbsp; Count APT Exception&lt;BR&gt;&amp;nbsp;&amp;nbsp; 0&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp; 828 0029a030&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; a020 Disabled 06907c38:069081d4 0f59e038&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 MTA&lt;BR&gt;&amp;nbsp;&amp;nbsp; 4&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 16fc 0029e980&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; b220 Enabled&amp;nbsp; 0690424c:069061d4 0021f4a8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 MTA (Finalizer)&lt;BR&gt;&amp;nbsp;&amp;nbsp; 5&amp;nbsp;&amp;nbsp;&amp;nbsp; 3 1c1c 002e71e8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1220 Enabled&amp;nbsp; 028f20f8:028f3f94 0021f4a8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp;&amp;nbsp; 6&amp;nbsp;&amp;nbsp;&amp;nbsp; 4 1244 0f6fa778&amp;nbsp;&amp;nbsp;&amp;nbsp; 80a220 Enabled&amp;nbsp; 00000000:00000000 0021f4a8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 MTA (Threadpool Completion Port)&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;&lt;FONT size=2&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; OSID&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Special thread type&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp; e20&amp;nbsp;&amp;nbsp;&amp;nbsp; DbgHelper &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&amp;nbsp;&amp;nbsp; 1e1c&amp;nbsp;&amp;nbsp;&amp;nbsp; GC &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3&amp;nbsp;&amp;nbsp; 1ed4&amp;nbsp;&amp;nbsp;&amp;nbsp; GC &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4&amp;nbsp;&amp;nbsp; 16fc&amp;nbsp;&amp;nbsp;&amp;nbsp; Finalizer &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 5&amp;nbsp;&amp;nbsp; 1c1c&amp;nbsp;&amp;nbsp;&amp;nbsp; ADUnloadHelper &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 6&amp;nbsp;&amp;nbsp; 1244&amp;nbsp;&amp;nbsp;&amp;nbsp; Timer&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This posting is provided "AS IS" with no warranties, and confers no rights.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=457756" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/yunjin/archive/tags/Rotor+code+explanation/default.aspx">Rotor code explanation</category><category domain="http://blogs.msdn.com/yunjin/archive/tags/.NET+programming+gotcha+and+debugging+tips/default.aspx">.NET programming gotcha and debugging tips</category><category domain="http://blogs.msdn.com/yunjin/archive/tags/CLR+internal+and+Misc/default.aspx">CLR internal and Misc</category></item><item><title>Thread, System.Threading.Thread, and !Threads (II)</title><link>http://blogs.msdn.com/yunjin/archive/2005/08/29/457150.aspx</link><pubDate>Mon, 29 Aug 2005 19:30:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:457150</guid><dc:creator>yunjin</dc:creator><slash:comments>2</slash:comments><comments>http://blogs.msdn.com/yunjin/comments/457150.aspx</comments><wfw:commentRss>http://blogs.msdn.com/yunjin/commentrss.aspx?PostID=457150</wfw:commentRss><description>&lt;P&gt;With knowledge in my &lt;A href="https://blogs.msdn.com/yunjin/archive/2005/08/25/456355.aspx"&gt;previous blog&lt;/A&gt;, we could avoid some mistakes in .NET programming.&lt;/P&gt;
&lt;P&gt;A &lt;EM&gt;C++ Thread&lt;/EM&gt; is very resource heavy. It is associated with a lot of dynamically allocated memory and some OS handles. So it had better to be cleaned up ASAP after its corresponding &lt;EM&gt;OS thread&lt;/EM&gt; dies. &lt;EM&gt;C++ Thread&lt;/EM&gt; class has a reference count. For its object to be deleted, the ref count has to be dropped to 0 (Rotor: &lt;EM&gt;Thread::DecExternalCount&lt;/EM&gt; in &lt;EM&gt;vm\threads.cpp&lt;/EM&gt;). One interesting point is that the &lt;EM&gt;C# Thread&lt;/EM&gt; object actually keeps a reference to its associated &lt;EM&gt;C++ Thread&lt;/EM&gt;, so a live &lt;EM&gt;C# Thread&lt;/EM&gt; object could keep its &lt;EM&gt;C++ Thread&lt;/EM&gt; from being deleted even if the &lt;EM&gt;OS thread&lt;/EM&gt; is already dead. (On the other hand,&lt;EM&gt; C++ Thread&lt;/EM&gt; also has a reference to &lt;EM&gt;C# Thread&lt;/EM&gt;, but it will break the circle when its own ref count drops to 1). Because &lt;EM&gt;C# Thread&lt;/EM&gt; is a managed object, its lifetime is mostly determined by users. Plus, &lt;EM&gt;C# Thread&lt;/EM&gt; class has a finalizer, so its lifetime will be extended at least one GC.&amp;nbsp; So if user code caches the &lt;EM&gt;C# Thread&lt;/EM&gt; objects or have some ill-behaved finalizers (in &lt;A href="https://blogs.msdn.com/yunjin/archive/2005/07/05/435726.aspx"&gt;another blog entry&lt;/A&gt;, I mentioned wrong-doing finalizer on one object could prevent all other object's fianlizer from running), "dead" &lt;EM&gt;C++ Thread&lt;/EM&gt; objects&amp;nbsp;may accumulate over time and some "memory leak" will be observed.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;I have an example here to demo the problem and how to debug it using windbg + SOS. In this process, there are 202 &lt;EM&gt;C++ Thread&lt;/EM&gt; objects. Among which 160 are "dead", meaning their associated &lt;EM&gt;OS threads&lt;/EM&gt; are dead. Number of total threads in &lt;EM&gt;Thread Store&lt;/EM&gt; and dead/unstarted threads are showed in "!threads" output. For a "live" thread, OS and debugger thread ID are printed out for the entry, for a "dead" thread, "XXX" is marked at beginning of the line:&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px"&gt;
&lt;P&gt;&lt;EM&gt;&lt;FONT size=2&gt;0:043&amp;gt; !threads&lt;BR&gt;ThreadCount: 202&lt;BR&gt;UnstartedThread: 0&lt;BR&gt;BackgroundThread: 1&lt;BR&gt;PendingThread: 0&lt;BR&gt;DeadThread: 160&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; PreEmptive&amp;nbsp;&amp;nbsp; GC Alloc&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Lock&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ID&amp;nbsp; ThreadOBJ&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; State&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; GC&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Context&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Domain&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Count APT Exception&lt;BR&gt;&amp;nbsp; 0 0x1138 0x0015a298&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x20 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1 Ukn&lt;BR&gt;&amp;nbsp; 1 0x1148 0x00152530&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1220 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn (Finalizer)&lt;BR&gt;&amp;nbsp; 3 0x114c 0x00177548&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp; 4 0x1150 0x00177878&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp; 5 0x1154 0x00177c08&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;… &lt;BR&gt;&amp;nbsp;42 0x11e4 0x00180460&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;&amp;nbsp;22 0x11e8 0x00180838&amp;nbsp;&amp;nbsp; 0x2001020 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00180c10&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00180fe8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x001813c0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00181750&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00181b28&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x00181f00&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x001822d8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 0x00149ac8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;… //continue with a huge list&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&lt;BR&gt;Now I want to find out why all the "dead" &lt;EM&gt;C++ Thread&lt;/EM&gt; objects are still around. First I could check its ref count if I have symbols for mscorwks.&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px"&gt;
&lt;P&gt;&lt;EM&gt;&lt;FONT size=2&gt;//0x00180c10 is a dead ThreadOBJ I picked from !Threads output&lt;BR&gt;0:043&amp;gt; dt mscorwks!Thread 0x00180c10 m_ExternalRefCount&lt;BR&gt;&amp;nbsp;&amp;nbsp; +0x0cc m_ExternalRefCount : 1&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Since the ref count is 1, if this &lt;EM&gt;C++ Thread&lt;/EM&gt; object has a &lt;EM&gt;C# Thread&lt;/EM&gt; object associated with it, the C# object must be the last reference. I could verify if that is the case by checking the C++ object's &lt;EM&gt;m_ExposedObject&lt;/EM&gt; field. It is a weak GC handle (a unmovable pointer to GC reference which doesn't counted as root of the GC object), so dereference it will get the managed object. As mentioned before, &lt;EM&gt;C++ Thread&lt;/EM&gt; object also has a strong handle (&lt;EM&gt;m_StrongHndToExposedObject&lt;/EM&gt; field)&amp;nbsp;to the &lt;EM&gt;C# object&lt;/EM&gt;, but it already cleared the strong handle when ref count drops to 1 to avoid circular reference.&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px"&gt;
&lt;P&gt;&lt;EM&gt;&lt;FONT size=2&gt;0:043&amp;gt; dt mscorwks!Thread 0x00180c10 m_ExposedObject&lt;BR&gt;&amp;nbsp;&amp;nbsp; +0x0c0 m_ExposedObject : 0x00a71054 &lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;&lt;FONT size=2&gt;0:043&amp;gt; dp 0x00a71054 l1&lt;BR&gt;00a71054&amp;nbsp; 00c5c714&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;&lt;FONT size=2&gt;0:043&amp;gt; !do 00c5c714&lt;BR&gt;Name: System.Threading.Thread&lt;BR&gt;MethodTable 0x79bb8384&lt;BR&gt;EEClass 0x79bb85b0&lt;BR&gt;Size 60(0x3c) bytes&lt;BR&gt;GC Generation: 0&lt;BR&gt;mdToken: 0x020000eb&amp;nbsp; (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)&lt;BR&gt;FieldDesc*: 0x79bb8614&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MT&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Field&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Offset&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Attr&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Value Name&lt;BR&gt;0x79bb8384 0x4000330&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_Context&lt;BR&gt;0x79bb8384 0x4000331&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_LogicalCallContext&lt;BR&gt;0x79bb8384 0x4000332&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0xc&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_IllogicalCallContext&lt;BR&gt;0x79bb8384 0x4000333&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x10&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_Name&lt;BR&gt;0x79bb8384 0x4000334&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x14&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_ExceptionStateInfo&lt;BR&gt;0x79bb8384 0x4000335&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x18&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_Delegate&lt;BR&gt;0x79bb8384 0x4000336&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_PrincipalSlot&lt;BR&gt;0x79bb8384 0x4000337&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x20&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_ThreadStatics&lt;BR&gt;0x79bb8384 0x4000338&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x24&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_ThreadStaticsBits&lt;BR&gt;0x79bb8384 0x4000339&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x28&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_CurrentCulture&lt;BR&gt;0x79bb8384 0x400033a&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x2c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_CurrentUICulture&lt;BR&gt;0x79bb8384 0x400033b&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Int32&amp;nbsp;&amp;nbsp; instance 2 m_Priority&lt;BR&gt;0x79bb8384 0x400033c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x34&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Int32&amp;nbsp;&amp;nbsp; instance 1575952 DONT_USE_InternalThread&lt;BR&gt;0x79bb8384 0x400033d&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; shared&amp;nbsp;&amp;nbsp; static m_LocalDataStoreMgr&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;gt;&amp;gt; Domain:Value 0x00149ac8:0x00c05338 &amp;lt;&amp;lt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Then I want to check root of the &lt;EM&gt;C# Thread&lt;/EM&gt; object to see who keeps it alive:&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px"&gt;
&lt;P&gt;&lt;EM&gt;&lt;FONT size=2&gt;0:043&amp;gt; !gcroot 00c5c714&lt;BR&gt;Scan Thread 0 (0x1138)&lt;BR&gt;ESP:12f69c:Root:0xc5b3f4(System.Object[])-&amp;gt;0xc5c714(System.Threading.Thread)&lt;BR&gt;…&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;So there is an array who keeps a reference to a "dead" &lt;EM&gt;C# Thread&lt;/EM&gt;. This looks interesting. I could check all other &lt;EM&gt;C# Thread&lt;/EM&gt; objects in the process using !DumpHeap command. !DumpHeap could dump objects in GC heap for a particular type specified by "-type" option:&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px"&gt;
&lt;P&gt;&lt;EM&gt;&lt;FONT size=2&gt;0:043&amp;gt; !DumpHeap -type System.Threading.Thread&lt;BR&gt;&amp;nbsp;&amp;nbsp; Address&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MT&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Size&amp;nbsp; Gen&lt;BR&gt;0x00c054c8 0x79bb8384&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 60&amp;nbsp;&amp;nbsp;&amp;nbsp; 1 System.Threading.Thread &lt;BR&gt;0x00c5b730 0x79bc81d4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 28&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.ThreadStart &lt;BR&gt;0x00c5b74c 0x79bb8384&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 60&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.Thread &lt;BR&gt;0x00c5b7bc 0x79bc81d4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 28&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.ThreadStart &lt;BR&gt;0x00c5b7d8 0x79bb8384&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 60&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.Thread &lt;BR&gt;0x00c5b820 0x79bc81d4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 28&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.ThreadStart &lt;BR&gt;0x00c5b83c 0x79bb8384&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 60&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.Thread &lt;BR&gt;0x00c5b884 0x79bc81d4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 28&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.ThreadStart &lt;BR&gt;0x00c5b8a0 0x79bb8384&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 60&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.Thread &lt;BR&gt;0x00c5b8e8 0x79bc81d4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 28&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.ThreadStart &lt;BR&gt;0x00c5b904 0x79bb8384&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 60&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.Thread &lt;BR&gt;0x00c5b94c 0x79bc81d4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 28&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.ThreadStart &lt;BR&gt;0x00c5b968 0x79bb8384&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 60&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.Thread &lt;BR&gt;0x00c5b9b0 0x79bc81d4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 28&amp;nbsp;&amp;nbsp;&amp;nbsp; 2 System.Threading.ThreadStart &lt;BR&gt;…//long list&lt;BR&gt;total 401 objects&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Because !DumpHeap match type by string, so it also dumps &lt;EM&gt;ThreadStart&lt;/EM&gt; objects. Because every &lt;EM&gt;C# Thread&lt;/EM&gt; object created by user code always has a &lt;EM&gt;ThreadStart&lt;/EM&gt; object (but &lt;EM&gt;C# Thread&lt;/EM&gt; created by &lt;EM&gt;System.Thread.CurrentThread &lt;/EM&gt;may not have a &lt;EM&gt;ThreadStart&lt;/EM&gt;), so they show up as a pair. among 401 such objects,&amp;nbsp;200 are &lt;EM&gt;C# Thread&lt;/EM&gt; objects, roughly match the number of &lt;EM&gt;C++ Thread&lt;/EM&gt; objects (the number doesn't have to be the same because not every &lt;EM&gt;C++ Thread&lt;/EM&gt; object has a C# counterpart created). Generation for most of &lt;EM&gt;C# Thread&lt;/EM&gt; objects are 2, meaning they already survive at least 2 GCs. When I track roots of those &lt;EM&gt;C# Thread&lt;/EM&gt; objects, they all point to the array. In this case, we need to look closely to the source to see whether it is necessary to cache all the &lt;EM&gt;C# Thread&lt;/EM&gt; objects in an array.&lt;/P&gt;
&lt;P&gt;Another related topic is that CLR relies on &lt;EM&gt;DLL_THREAD_DETACH&lt;/EM&gt; notification to mscorwks.dll's DllMain (Rotor: &lt;EM&gt;EEDllMain&lt;/EM&gt; in &lt;EM&gt;vm\ceemain.cpp&lt;/EM&gt;) to know an OS thread is dead, thus detach the related &lt;EM&gt;C++ Thread&lt;/EM&gt;. Using &lt;EM&gt;TerminateThread&lt;/EM&gt; API is already notoriously bad&amp;nbsp;in unmanaged programming, here we see another reason not to call it in managed code: if &lt;EM&gt;TerminateThread&lt;/EM&gt; is called on a managed OS thread, among other bad effect (e.g. back out code not executed), CLR will not get thread detach notification. Because &lt;EM&gt;C++ Thread&lt;/EM&gt; object has references to &lt;EM&gt;OS thread's&lt;/EM&gt; stack address, failing to detach it from the OS thread will cause crash at random place.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This posting is provided "AS IS" with no warranties, and confers no rights.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=457150" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/yunjin/archive/tags/Rotor+code+explanation/default.aspx">Rotor code explanation</category><category domain="http://blogs.msdn.com/yunjin/archive/tags/.NET+programming+gotcha+and+debugging+tips/default.aspx">.NET programming gotcha and debugging tips</category><category domain="http://blogs.msdn.com/yunjin/archive/tags/CLR+internal+and+Misc/default.aspx">CLR internal and Misc</category></item><item><title>Thread, System.Threading.Thread, and !Threads (I)</title><link>http://blogs.msdn.com/yunjin/archive/2005/08/25/456355.aspx</link><pubDate>Thu, 25 Aug 2005 20:26:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:456355</guid><dc:creator>yunjin</dc:creator><slash:comments>4</slash:comments><comments>http://blogs.msdn.com/yunjin/comments/456355.aspx</comments><wfw:commentRss>http://blogs.msdn.com/yunjin/commentrss.aspx?PostID=456355</wfw:commentRss><description>&lt;P&gt;If you use SOS’s !Threads command during debugging a lot,&amp;nbsp; you should be familiar with such output:&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px"&gt;
&lt;P&gt;&lt;BR&gt;&lt;EM&gt;&lt;FONT size=2&gt;0:003&amp;gt; !threads&lt;BR&gt;PDB symbol for mscorwks.dll not loaded&lt;BR&gt;Loaded Son of Strike data table version 5 from "C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorwks.dll"&lt;BR&gt;ThreadCount: 12&lt;BR&gt;UnstartedThread: 5&lt;BR&gt;BackgroundThread: 1&lt;BR&gt;PendingThread: 0&lt;BR&gt;DeadThread: 5&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; PreEmptive&amp;nbsp;&amp;nbsp; GC Alloc&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Lock&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ID&amp;nbsp; ThreadOBJ&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; State&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; GC&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Context&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Domain&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Count APT Exception&lt;BR&gt;&amp;nbsp; 0&amp;nbsp; 0xb74 0x0014f230&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x20 Enabled&amp;nbsp; 0x00000000:0x00000000 x00149aa8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1 Ukn&lt;BR&gt;&amp;nbsp; 2&amp;nbsp; 0xb58 0x00157cf8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1220 Enabled&amp;nbsp; 0x00000000:0x00000000 x00149aa8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn (Finalizer)&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x001665f0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 x00149aa8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x0016d348&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1400 Enabled&amp;nbsp; 0x00000000:0x00000000 x00149aa8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x0016d510&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 x00149aa8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x0016d9d0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1400 Enabled&amp;nbsp; 0x00000000:0x00000000 x00149aa8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x0016db98&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 x00149aa8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x0016e248&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1400 Enabled&amp;nbsp; 0x00000000:0x00000000 x00149aa8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x0016e410&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 x00149aa8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x0016e740&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1400 Enabled&amp;nbsp; 0x00000000:0x00000000 x00149aa8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x0016e908&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1820 Enabled&amp;nbsp; 0x00000000:0x00000000 x00149aa8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;BR&gt;XXX&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0x0016ec98&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1400 Enabled&amp;nbsp; 0x00000000:0x00000000 x00149aa8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 Ukn&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;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.&lt;BR&gt;&amp;nbsp;&lt;BR&gt;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 &lt;EM&gt;C# Thread&lt;/EM&gt; below) to control managed threads just as thread HANDLE is used to control to windows native threads. If you ever checked contents of a &lt;EM&gt;C# Thread&lt;/EM&gt; 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):&lt;BR&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px"&gt;
&lt;P&gt;&lt;BR&gt;&lt;EM&gt;&lt;FONT size=2&gt;0:003&amp;gt; !DumpObj 0x00c03248 &lt;BR&gt;Name: System.Threading.Thread&lt;BR&gt;MethodTable 0x79bb8384&lt;BR&gt;EEClass 0x79bb85b0&lt;BR&gt;Size 60(0x3c) bytes&lt;BR&gt;GC Generation: 0&lt;BR&gt;mdToken: 0x020000eb&amp;nbsp; (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)&lt;BR&gt;FieldDesc*: 0x79bb8614&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MT&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Field&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Offset&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Attr&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Value Name&lt;BR&gt;0x79bb8384 0x4000330&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_Context&lt;BR&gt;0x79bb8384 0x4000331&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_LogicalCallContext&lt;BR&gt;0x79bb8384 0x4000332&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0xc&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_IllogicalCallContext&lt;BR&gt;0x79bb8384 0x4000333&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x10&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_Name&lt;BR&gt;0x79bb8384 0x4000334&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x14&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_ExceptionStateInfo&lt;BR&gt;0x79bb8384 0x4000335&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x18&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_Delegate&lt;BR&gt;0x79bb8384 0x4000336&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x1c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_PrincipalSlot&lt;BR&gt;0x79bb8384 0x4000337&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x20&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_ThreadStatics&lt;BR&gt;0x79bb8384 0x4000338&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x24&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_ThreadStaticsBits&lt;BR&gt;0x79bb8384 0x4000339&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x28&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_CurrentCulture&lt;BR&gt;0x79bb8384 0x400033a&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x2c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp; instance 0x00000000 m_CurrentUICulture&lt;BR&gt;0x79bb8384 0x400033b&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Int32&amp;nbsp;&amp;nbsp; instance 2 m_Priority&lt;BR&gt;0x79bb8384 0x400033c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x34&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Int32&amp;nbsp;&amp;nbsp; instance 1498008 DONT_USE_InternalThread&lt;BR&gt;0x79bb8384 0x400033d&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CLASS&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; shared&amp;nbsp;&amp;nbsp; static m_LocalDataStoreMgr&lt;BR&gt;&lt;/FONT&gt;&lt;/EM&gt;&amp;nbsp;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&lt;BR&gt;CLR actually needs much more information about a thread than fields of the &lt;EM&gt;C# Thread&lt;/EM&gt; 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&amp;nbsp; it &lt;EM&gt;C++ Thread&lt;/EM&gt;) to keep all information for an OS native thread (&lt;EM&gt;OS thread&lt;/EM&gt;). In Rotor, this class is defined in &lt;EM&gt;vm\threads.h.&lt;/EM&gt; &lt;BR&gt;&amp;nbsp;&lt;BR&gt;CLR needs to create a &lt;EM&gt;C++ Thread&lt;/EM&gt; object for every &lt;EM&gt;OS thread&lt;/EM&gt; EE knows of, that is, every thread which ever ran managed code. Such threads could either be (a) created explicitly by users using &lt;EM&gt;C# Thread.Start&lt;/EM&gt;, (b) an unmanaged &lt;EM&gt;OS thread&lt;/EM&gt; who has ever visited managed world, e.g, through interop, or (c) a special &lt;EM&gt;OS thread&lt;/EM&gt; in CLR which might run managed code. For case a, when a user creates a &lt;EM&gt;C# Thread&lt;/EM&gt; object, CLR will create a &lt;EM&gt;C++ Thread&lt;/EM&gt; object, link it to the &lt;EM&gt;C# Thread&lt;/EM&gt; object, and mark it as unstarted (Rotor: &lt;EM&gt;SetupUnstartedThread&lt;/EM&gt; in &lt;EM&gt;vm\threads.cpp&lt;/EM&gt;). Once &lt;EM&gt;Start&lt;/EM&gt; method is called on the &lt;EM&gt;C# Thread&lt;/EM&gt; object, CLR will create an &lt;EM&gt;OS thread&lt;/EM&gt;, in the &lt;EM&gt;OS thread&lt;/EM&gt;’s ThreadPproc (Rotor:&lt;EM&gt; ThreadNative::KickOffThread&lt;/EM&gt; in &lt;EM&gt;vm\ComSynchronizable.cpp&lt;/EM&gt;), CLR will save the &lt;EM&gt;C++ Thread&lt;/EM&gt; object’s address to the &lt;EM&gt;OS thread&lt;/EM&gt;'s TLS (Thread Local Storage) and mark it to be started (Rotor: &lt;EM&gt;ThreadStore::TransferStartedThread&lt;/EM&gt; in &lt;EM&gt;vm\threads.cpp&lt;/EM&gt;); For case b, at entry point for an unmanaged &lt;EM&gt;OS thread&lt;/EM&gt; to managed world, CLR will create a &lt;EM&gt;C++ Thread&lt;/EM&gt; object and also use TLS to associate it with the &lt;EM&gt;OS thread&lt;/EM&gt; (Rotor: &lt;EM&gt;SetupThread&lt;/EM&gt; in &lt;EM&gt;vm\threads.cpp&lt;/EM&gt;); Case c is similar to case b, except CLR might set up &lt;EM&gt;C++ Thread&lt;/EM&gt; earlier. &lt;BR&gt;&amp;nbsp;&lt;BR&gt;In any case, the &lt;EM&gt;C++ Thread&lt;/EM&gt; 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&lt;EM&gt; OS thread&lt;/EM&gt;'s TLS. In that sense, the &lt;EM&gt;C# Thread&lt;/EM&gt; is more like a managed proxy for the &lt;EM&gt;C++ Thread&lt;/EM&gt;. In fact, for case b and c, there will be no &lt;EM&gt;C# Thread&lt;/EM&gt; objects created unless users call &lt;EM&gt;System.Threading.Thread.CurrentThread&lt;/EM&gt;. &lt;EM&gt;C++ Thread&lt;/EM&gt; tracks its corresponding &lt;EM&gt;C# Thread&lt;/EM&gt; using a GCHandle (&lt;EM&gt;m_ExposedObject&lt;/EM&gt; field in &lt;EM&gt;C++ Thread&lt;/EM&gt; object); &lt;EM&gt;C# Thread&lt;/EM&gt; tracks its &lt;EM&gt;C++ Thread&lt;/EM&gt; using a native pointer(&lt;EM&gt;DONT_USE_InternalThread&lt;/EM&gt; field in &lt;EM&gt;C# Thread&lt;/EM&gt;). You could verify this circular reference in debugger if you have symbols for mscorwks.dll:&lt;BR&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px"&gt;
&lt;P&gt;&lt;BR&gt;&lt;EM&gt;&lt;FONT size=2&gt;0:003&amp;gt; !do 0x00c03248 &lt;BR&gt;Name: System.Threading.Thread&lt;BR&gt;…&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MT&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Field&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Offset&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Attr&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Value &lt;BR&gt;…&lt;BR&gt;0x79bb8384 0x400033c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0x34&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Int32&amp;nbsp;&amp;nbsp; instance 1498008 DONT_USE_InternalThread&lt;BR&gt;…&lt;BR&gt;&amp;nbsp;&lt;BR&gt;0:013&amp;gt; dt mscorwks!Thread 0n1498008&lt;BR&gt;…&lt;BR&gt;+0x0c0 m_ExposedObject&amp;nbsp; : 0x00a710e4&lt;BR&gt;…&lt;BR&gt;&amp;nbsp;&lt;BR&gt;0:013&amp;gt; dp 0x00a710e4 l1&lt;BR&gt;00a710e4&amp;nbsp; 00c03248&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;CLR uses a data structure called &lt;EM&gt;Thread Store&lt;/EM&gt; (Rotor: &lt;EM&gt;ThreadStore&lt;/EM&gt; in &lt;EM&gt;vm\threads.h&lt;/EM&gt;) to keep all &lt;EM&gt;C++ Threads&lt;/EM&gt;. What you see in output of !Threads command is actually list of &lt;EM&gt;C++ Threads&lt;/EM&gt; in &lt;EM&gt;Thread Store&lt;/EM&gt;. The “ThreadOBJ” field is address of a &lt;EM&gt;C++ Thread&lt;/EM&gt; object. Other fields are important information about the &lt;EM&gt;C++ Thread&lt;/EM&gt;, including OS thread ID for the corresponding &lt;EM&gt;OS thread&lt;/EM&gt;. Here you could see not every live &lt;EM&gt;OS thread&lt;/EM&gt; has a &lt;EM&gt;C++ Thread&lt;/EM&gt;, which could be explained by the fact that CLR only creates a &lt;EM&gt;C++ Thread&lt;/EM&gt; for an &lt;EM&gt;OS thread&lt;/EM&gt; which ever runs managed code. Meanwhile you may also find some &lt;EM&gt;C++ Threads&lt;/EM&gt; don't have an &lt;EM&gt;OS thread&lt;/EM&gt; associated with them (the ID fields are “XXX”). They are either (a)unstarted, (b) failed to started, (c)used to represent an live &lt;EM&gt;OS thread&lt;/EM&gt; which is now dead. For case (a), CLR will wait until &lt;EM&gt;C# Thread.Start&lt;/EM&gt; is called and create an &lt;EM&gt;OS thread&lt;/EM&gt; for it then; for (b) and (c), the &lt;EM&gt;C++ Thread&lt;/EM&gt;s object will be deleted some time later.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This posting is provided "AS IS" with no warranties, and confers no rights.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=456355" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/yunjin/archive/tags/Rotor+code+explanation/default.aspx">Rotor code explanation</category><category domain="http://blogs.msdn.com/yunjin/archive/tags/.NET+programming+gotcha+and+debugging+tips/default.aspx">.NET programming gotcha and debugging tips</category><category domain="http://blogs.msdn.com/yunjin/archive/tags/CLR+internal+and+Misc/default.aspx">CLR internal and Misc</category></item><item><title>Special threads in CLR</title><link>http://blogs.msdn.com/yunjin/archive/2005/07/05/435726.aspx</link><pubDate>Tue, 05 Jul 2005 19:52:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:435726</guid><dc:creator>yunjin</dc:creator><slash:comments>15</slash:comments><comments>http://blogs.msdn.com/yunjin/comments/435726.aspx</comments><wfw:commentRss>http://blogs.msdn.com/yunjin/commentrss.aspx?PostID=435726</wfw:commentRss><description>&lt;P&gt;&lt;STRONG&gt;Question:&lt;/STRONG&gt; How many threads does a typical managed process have when it just starts to run?&amp;nbsp;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Answer:&lt;/STRONG&gt; regardless how many threads the user creates, there are at least 3 threads for a common managed process after CLR starts up: a main thread which starts CLR and run user's Main method, CLR debugger helper thread which provides debugging service for interop debuggers like Visual Studio, and the finalizer thread which runs finalizers for unreachable objects. Depends on what the program does, CLR might create more threads to perform special tasks.&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Sometimes it is important to know what "special" threads would be created in CLR so we could understand better the implicit impact of our managed programs. Here is a list of most common special threads:&amp;nbsp;&lt;/P&gt;
&lt;P&gt;1. Finalizer thread. The thread is to run finalizers for "dead" objects. This thread is created when GC heap is initialized during EE start up. In Rotor, the thread proc for the thread is &lt;EM&gt;GCHeap::FinalizerThreadStart&lt;/EM&gt; in &lt;EM&gt;vm\gcee.cpp&lt;/EM&gt;. Because GC is undeterministic and finalizers are executed in a separate thread, you can't predict when exactly an object will be finalized. Because there is only one thread to run all finalizers, if one finalizer is blocked, no other finalizers could run. So it is discouraged to take any lock in finalizer. Also see &lt;a href="http://blogs.msdn.com/maoni/archive/2004/11/04/252697.aspx"&gt;Maoni Stephens's blog&lt;/A&gt; for details about finalizer thread.&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;2. Debugger helper thread. As its name suggests, this thread helps debuggers (mixed mode&amp;nbsp;and managed debugger, but not pure unmanaged debug like windbg)&amp;nbsp;to get information of the managed process and to execute certain debugging operations. The thread is created when EE initializes debugger during start up. In Rotor, the thread proc for this thread is &lt;EM&gt;DebuggerRCThread::ThreadProcStatic (debug\ee\Rcthread.cpp)&lt;/EM&gt;. Also see &lt;a href="http://blogs.msdn.com/jmstall/archive/2004/10/13/241828.aspx"&gt;Mike Stall's blog &lt;/A&gt;about impact of this helper thread。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;3. Concurrent GC thread (doesn't exist in Rotor). As explained in &lt;a href="http://blogs.msdn.com/maoni/archive/2004/09/25/234273.aspx"&gt;Maoni&lt;/A&gt; and &lt;a href="http://blogs.msdn.com/clyon/archive/2004/09/08/226981.aspx"&gt;Chris Lyon's blog&lt;/A&gt;, concurrent GC is a special GC mode which allows garbage to be collected while managed threads are running simultaneously. To achieve this goal, CLR creates a thread to perform GC concurrently with user threads. The thread is only created when CLR decides to do a concurrent GC (even when concurrent GC mode is on, not every GC is concurrent, read Maoni's blog for details) and will be recycled when there are no concurrent GC work to do. &lt;BR&gt;&lt;/P&gt;
&lt;P&gt;4. Server GC threads (doesn't exist in Rotor). Maoni and Chris also explained Server GC mode where on multi-process machine CLR creates one GC heap for each CPU and one thread to do GC for each heap. When Server GC mode is enabled, server GC threads will be created at EE start up time when GC heaps are initialized. &lt;/P&gt;
&lt;P&gt;&lt;BR&gt;5. App Domain unload helper thread. In CLR V1.X, when a thread requests to unload an App Domain and the thread is in that App Domain itself, it needs to create a worker thread to do the unloading work. The worker thread will be dead once the target AD is unloaded. In Rotor, the thread starts with &lt;EM&gt;UnloadThreadWorker.ThreadStart (bcl\system\Appdomain.cs)&lt;/EM&gt;. In Whidbey, all AD unload work is performed in a special thread regardless whether the requesting thread is in the unloading domain. The helper thread is created when first non-default App Domain is created (default domain is never unloaded) and will stay alive since then. Also see &lt;a href="http://blogs.msdn.com/cbrumme/archive/2003/06/01/51466.aspx"&gt;Chris Brumme's blog&lt;/A&gt; about details of AD unload.&lt;/P&gt;
&lt;P&gt;6. Threadpool threads. Depends on how a program use CLR threadpool, CLR might create threads of a varieties of types. There is only one thread for some thread type. For other types, number of threads is related to number of CPUs, the work load, and some user configurable settings. The thread types including wait threads (threads to perform asynchronized wait, could be more than one); worker threads (threads to execute user work item, could be more than one); Completion port threads (threads wait for completion port IO in Windows, could be more than one, doesn't exist in Rotor); Gate thread (thread help to monitor status of completion port threads and worker threads, only one); Timer thread (thread manages timer queue, only one). &lt;BR&gt;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=435726" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/yunjin/archive/tags/Rotor+code+explanation/default.aspx">Rotor code explanation</category><category domain="http://blogs.msdn.com/yunjin/archive/tags/CLR+internal+and+Misc/default.aspx">CLR internal and Misc</category></item><item><title>Thread safety of Timer callbacks</title><link>http://blogs.msdn.com/yunjin/archive/2005/05/08/415480.aspx</link><pubDate>Sun, 08 May 2005 11:12:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:415480</guid><dc:creator>yunjin</dc:creator><slash:comments>5</slash:comments><comments>http://blogs.msdn.com/yunjin/comments/415480.aspx</comments><wfw:commentRss>http://blogs.msdn.com/yunjin/commentrss.aspx?PostID=415480</wfw:commentRss><description>&lt;P class=MsoNormal&gt;I didn't realize I've stopped blogging for 1 year. What a shame! Fortunately I didn’t waste the time: we ship Whidbey Beta1 and Beta2 in the past year! Now with Beta2 out of door, I have more spare time for blogging. :)&lt;/P&gt;&lt;BR&gt;
&lt;P class=MsoNormal&gt;Today I want to talk about some interesting facts about Timer in CLR. There is an example for how to use timer in MSDN:&amp;nbsp;&lt;A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemthreadingtimerclasstopic.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemthreadingtimerclasstopic.asp&lt;/A&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;/P&gt;&lt;BR&gt;
&lt;P class=MsoNormal&gt;This sample starts a timer and does certain things when the timer fires for certain times, like killing the timer. However, this sample has a bug which will cause trouble in stress scenario. To demonstrate the problem, I made a little change to the code:&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;using System;&lt;BR&gt;using System.Threading;&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;class TimerExample&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;static void Main()&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;AutoResetEvent autoEvent&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = new AutoResetEvent(false);&lt;BR&gt;&amp;nbsp;&amp;nbsp;StatusChecker&amp;nbsp; statusChecker = new StatusChecker(100);&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;&amp;nbsp;&amp;nbsp;// Create the delegate that invokes methods for the timer.&lt;BR&gt;&amp;nbsp;&amp;nbsp;TimerCallback timerDelegate = &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;new TimerCallback(statusChecker.CheckStatus);&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;&amp;nbsp;&amp;nbsp;Console.WriteLine("{0} Creating timer.\n", &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;DateTime.Now.ToString("h:mm:ss.fff"));&lt;BR&gt;&amp;nbsp;&amp;nbsp;Timer stateTimer = &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;new Timer(timerDelegate, autoEvent, 0, 10);&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;&amp;nbsp;&amp;nbsp;// start another thread to post work items to thread pool&lt;BR&gt;&amp;nbsp;&amp;nbsp;Thread t = new Thread (new ThreadStart (PostWortItem));&lt;BR&gt;&amp;nbsp;&amp;nbsp;t.Start ();&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;&amp;nbsp;&amp;nbsp;// When autoEvent signals, dispose of &lt;BR&gt;&amp;nbsp;&amp;nbsp;// the timer.&lt;BR&gt;&amp;nbsp;&amp;nbsp;autoEvent.WaitOne();&lt;BR&gt;&amp;nbsp;&amp;nbsp;stateTimer.Dispose();&lt;BR&gt;&amp;nbsp;&amp;nbsp;Console.WriteLine("\nDestroying timer.");&lt;BR&gt;&amp;nbsp;}&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;&amp;nbsp;// a Thread proc which keeps posting work items to thread pool&lt;BR&gt;&amp;nbsp;static void PostWortItem ()&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;// Post some user work items to thread pool&lt;BR&gt;&amp;nbsp;&amp;nbsp;for (int i = 0; i &amp;lt; 1000; i++)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ThreadPool.QueueUserWorkItem (new WaitCallback (WorkItem));&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Thread.Sleep (10);&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;}&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;&amp;nbsp;// An nop work item for thread pool&lt;BR&gt;&amp;nbsp;static void WorkItem (object o)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;Thread.Sleep (500);&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;}&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;class StatusChecker&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;int invokeCount, maxCount;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;public StatusChecker(int count)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;invokeCount&amp;nbsp; = 0;&lt;BR&gt;&amp;nbsp;&amp;nbsp;maxCount = count;&lt;BR&gt;&amp;nbsp;}&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;&amp;nbsp;// This method is called by the timer delegate.&lt;BR&gt;&amp;nbsp;public void CheckStatus(Object stateInfo)&lt;BR&gt;&amp;nbsp;{&amp;nbsp;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;Console.WriteLine("Checking status " + (++invokeCount));&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;&amp;nbsp;&amp;nbsp;if(invokeCount == maxCount)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;//signal Main.&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;autoEvent.Set();&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;}&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P class=MsoNormal&gt;Basically I added another thread to keep posting work items to threadpool, but the rest part is still expected to behave the same: when the timer fires the 100&lt;SUP&gt;th&lt;/SUP&gt; time, it should set an event so the main thread would stop the timer.&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;In one of 5 runs in my machine, I got such output:&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT size=2&gt;5:48:07.625 Creating timer.&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT size=2&gt;Checking status 1&lt;BR&gt;Checking status 2&lt;BR&gt;Checking status 3&lt;BR&gt;Checking status 4&lt;BR&gt;…&lt;BR&gt;Checking Status 93&lt;BR&gt;Checking Status 94&lt;BR&gt;Checking Status 95&lt;BR&gt;Checking Status 96&lt;BR&gt;Checking Status 97&lt;BR&gt;Checking Status 98&lt;BR&gt;Checking Status 102&lt;BR&gt;Checking Status 99&lt;BR&gt;Checking Status 103&lt;BR&gt;Checking Status 104&lt;BR&gt;Checking Status 105&lt;BR&gt;…&lt;BR&gt;Checking Status 698&lt;BR&gt;Checking Status 700&lt;BR&gt;Checking Status 701&lt;BR&gt;Checking Status 703&lt;BR&gt;Checking Status 703&lt;BR&gt;Checking Status 704&lt;BR&gt;Checking Status 705&lt;BR&gt;…&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT size=2&gt;^C&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P class=MsoNormal dir=ltr&gt;It seems that &lt;SPAN&gt;&lt;EM&gt;invokeCount&lt;/EM&gt; &lt;/SPAN&gt;never hits 100 thus the program doesn't stop and some other sequence in the output looks to be out of order.&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;How does this happen? First we need to understand how timer is implemented in CLR, who is executing the timer callbacks?&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;One simple idea would be putting all timers in a queue and having a dedicate thread doing something like this (pseudo code):&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P class=MsoNormal&gt;&lt;EM&gt;&lt;FONT size=2&gt;&lt;o:p&gt;while (true)&lt;BR&gt;{&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foreach (Timer t in timer queue)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (t.TimeToFire ())&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;t.InvokeCallback ();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Sleep(MinumInterval);&lt;BR&gt;}&lt;/o:p&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P class=MsoNormal&gt;However with this logic one lengthy timer callback would block all other timers. In CLR, we do have a timer queue and a dedicate timer thread. However the only job of timer thread is to maintain the timer queue, when a timer needs to fire, timer thread queue a work item to threadpool, then a thread pool's worker thread will pick up the work item and invoke the timer callback.&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;In Rotor's source, the timer thread's logic is in &lt;I&gt;vm/Win32threadpool.cpp&lt;/I&gt;, the thread proc is &lt;I&gt;&lt;SPAN&gt;ThreadpoolMgr::TimerThreadStart&lt;/SPAN&gt;&lt;/I&gt; and &lt;I&gt;&lt;SPAN&gt;ThreadpoolMgr::FireTimers&lt;/SPAN&gt;&lt;/I&gt; does most of interesting work. The pseudo code looks like:&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;while (true)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foreach (Timer t in timer queue)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (t.TimeToFire ())&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// put a work item to thread pool&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// to call timer cal back on t once&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WorkItem work = CallTimerCallbackOnce (t);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ThreadPool.QueueWorkItem (work);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;//MinumInterval is minum of next firing interval&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;// for all timers in the queue&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Sleep(MinumInterval);&lt;BR&gt;}&lt;/FONT&gt;&lt;/EM&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P class=MsoNormal&gt;The timer thread only guarantees to put timer callback requests to a queue in thread pool (&lt;I&gt;&lt;SPAN&gt;ThreadpoolMgr::QueueUserWorkItem&lt;/SPAN&gt;&lt;/I&gt;) in order of timer firing. But timer callbacks are not called in a serialized way. If a timer fires twice and there are more than one worker thread in thread pool, there's no guarantee that the first callback will be finished before the next callback starts. Therefore, it's not thread safe for timer callbacks to access shared data without locking. That's why the example in MSDN breaks:&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;when &lt;I&gt;&lt;SPAN&gt;CheckStatus&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt; &lt;/SPAN&gt;is executed in multiple threads, it's possible that "&lt;I&gt;&lt;SPAN&gt;if&lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;(invokeCount == maxCount)"&lt;/SPAN&gt;&lt;/I&gt;&amp;nbsp; will never be satisfied. Changing the code to this would make it more robust:&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;public void CheckStatus(Object stateInfo)&lt;BR&gt;{&amp;nbsp;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;int count = Interlocked.Increment (ref invokeCount);&lt;BR&gt;&amp;nbsp;&amp;nbsp;Console.WriteLine("Checking status " + count);&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&lt;EM&gt;&lt;FONT size=2&gt;&amp;nbsp;&amp;nbsp;if(count == maxCount)&lt;BR&gt;&amp;nbsp;&amp;nbsp;…&amp;nbsp;&lt;/FONT&gt;&lt;/EM&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P class=MsoNormal&gt;Another interesting thing about timer implementation is that when a client thread creates a new timer, it doesn't insert the timer to timer queue directly. Instead, it queues a user APC to the timer thread (see &lt;I&gt;&lt;SPAN&gt;ThreadpoolMgr::CreateTimerQueueTimer&lt;/SPAN&gt;&lt;/I&gt; and &lt;I&gt;&lt;SPAN&gt;InsertNewTimer&lt;/SPAN&gt;&lt;/I&gt;). Similar thing is done for updating (&lt;I&gt;&lt;SPAN&gt;ThreadpoolMgr::ChangeTimerQueueTimer&lt;/SPAN&gt;&lt;/I&gt; and &lt;I&gt;&lt;SPAN&gt;UpdateTimer&lt;/SPAN&gt;&lt;/I&gt;) and deleting timer (&lt;I&gt;&lt;SPAN&gt;ThreadpoolMgr:: DeleteTimerQueueTimer&lt;/SPAN&gt;&lt;/I&gt; and &lt;I&gt;&lt;SPAN&gt;DeregisterTimer&lt;/SPAN&gt;&lt;/I&gt;). That way, client threads don't need to synchronize to access the shared timer queue. After all, the timer thread is sleeping (alertable) for most of time. &lt;/P&gt;&lt;BR&gt;PS: to make the race happen more easily, I did more tweaks to the MSDN sample than the threadpool workitems, it should be obvious to you. ;) &lt;BR&gt;&lt;BR&gt;This posting is provided "AS IS" with no warranties, and confers no rights.&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=415480" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/yunjin/archive/tags/Rotor+code+explanation/default.aspx">Rotor code explanation</category><category domain="http://blogs.msdn.com/yunjin/archive/tags/.NET+programming+gotcha+and+debugging+tips/default.aspx">.NET programming gotcha and debugging tips</category><category domain="http://blogs.msdn.com/yunjin/archive/tags/CLR+internal+and+Misc/default.aspx">CLR internal and Misc</category></item><item><title>FCall and GC hole - first post about Rotor</title><link>http://blogs.msdn.com/yunjin/archive/2004/02/08/69906.aspx</link><pubDate>Mon, 09 Feb 2004 09:34:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:69906</guid><dc:creator>yunjin</dc:creator><slash:comments>5</slash:comments><comments>http://blogs.msdn.com/yunjin/comments/69906.aspx</comments><wfw:commentRss>http://blogs.msdn.com/yunjin/commentrss.aspx?PostID=69906</wfw:commentRss><description>&lt;P&gt;&lt;STRONG&gt;&lt;SPAN&gt;An exsample of FCall&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;My friend &lt;a href="https://blogs.msdn.com:443/joelpob"&gt;Joel Pobar&lt;/A&gt; had a great &lt;a href="https://blogs.msdn.com:443/joelpob/archive/2003/12/18/53745.aspx"&gt;post&lt;/A&gt; to demo how to add new code to Rotor&amp;nbsp;which exposes more&amp;nbsp;EE(Execution Engine)&amp;nbsp;internal information to managed world. This is a very good example covers both BCL and EE, and how the two parts interact with each other. As showed in this example, BCL code could call into EE by a special&amp;nbsp;type of method called “FCall“, like this:&lt;/SPAN&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P&gt;&lt;EM&gt;&lt;SPAN&gt;FCIMPL1(MethodBody *, COMMember::GetMethodBody, MethodDesc **ppMethod)&lt;/SPAN&gt;&lt;/EM&gt;&lt;I&gt;&lt;SPAN&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;{ &lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; MethodDesc* pMethod = *ppMethod;&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; METHODBODYREF MethodBodyObj = NULL;&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; HELPER_METHOD_FRAME_BEGIN_RET_0();&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; GCPROTECT_BEGIN(MethodBodyObj);&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; TypeHandle thMethodBody(g_Mscorlib.FetchClass(CLASS__METHOD_BODY));&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; MethodBodyObj = (METHODBODYREF)AllocateObject(thMethodBody.GetMethodTable());&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; Module* pModule = pMethod-&amp;gt;GetModule();&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; COR_ILMETHOD_DECODER MethodILHeader(pMethod-&amp;gt;GetILHeader(), pModule-&amp;gt;GetMDImport(), TRUE);&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; MethodBodyObj-&amp;gt;maxStackSize = MethodILHeader.GetMaxStack();&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; GCPROTECT_END();&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; HELPER_METHOD_POLL();&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; HELPER_METHOD_FRAME_END();&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;SPAN&gt;&lt;FONT color=#000000&gt;return&lt;/FONT&gt;&lt;/SPAN&gt; (MethodBody*)OBJECTREFToObject(MethodBodyObj);&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;}&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;FCIMPLEND &lt;/SPAN&gt;&lt;/EM&gt;&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&lt;SPAN&gt;As I said before I'd try to&amp;nbsp;explain some Rotor code in my blog, so let me start by analyzing a small problem in&amp;nbsp;that piece of&amp;nbsp;code. I won't call it a bug because it happens to be harmless here. But it does violate some CLR coding rules.&lt;/SPAN&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;&lt;SPAN&gt;Some basic bricks of an FCall&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;First let's take a glance of those&amp;nbsp;amazing&amp;nbsp;macros:&lt;/SPAN&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI class=MsoNormal&gt;FCIMPL1/FCIMPLEND: defined in vm/fcall.h. They just tweak calling convention between managed code (BCL)&amp;nbsp;and unmanaged code (EE). You can see different flavors of FCIMPL, which serve calls with different argument numbers or types (to match the argument passing and enregistering rules in managed world).&lt;o:p&gt;&lt;/o:p&gt; 
&lt;LI class=MsoNormal&gt;HELPER_METHOD_FRAME_BEGIN_RET_0/HELPER_METHOD_FRAME_END: defined in vm/fcall.h. For operations like GC (Garbage Collection) and EH (Exception Handling) to work correctly, some frames have to be set up in the stack, especially&amp;nbsp;at the boundary&amp;nbsp;between managed part and unmanaged part. Frames are a topic could take a blog entry itself, so I won't cover it too much here. What you need to know now is that for performance reason, an FCall doesn't set up a frame by default. So when an FCall wants to throw an exception or allow a GC to happen, it has to set up a HelperMethodFrame (vm/frames.h)&amp;nbsp;first. This job is done by HELPER_METHOD_FRAME_BEGIN* macro.HELPER_METHOD_FRAME_END is to tear down the frame from stack. All GC or exception throwing has to happen in the range guarded by this frame. In the code above, some operations&amp;nbsp;could trigger a GC (at least AllocateObject could do so, not sure about others. It would be a hard job to trace into each code path to find out whether a GC could happen), so a HelperMethodFrame has to be established before&amp;nbsp;that call.&amp;nbsp;&amp;nbsp;&lt;o:p&gt;&lt;/o:p&gt; 
&lt;LI class=MsoNormal&gt;GCPROTECT_BEGIN/GCPROTECT_END: defined in vm/frames.h. Similar to HELPER_METHOD_FRAME_BEGIN*, GCPROTECT_BEGIN is used to set up a GCFrame (vm/frames.h)&amp;nbsp;and GCPROTECT_END pop the frame out.&amp;nbsp;When a GC happens, it needs to find out all object references in stack to trace which objects in the managed heap are still alive, and when it moves the object (to compact the heap) it needs to update the references in stack with the new location of the objects. For managed code, JIT&amp;nbsp;generates all&amp;nbsp;information&amp;nbsp;needed by GC. But for unmanaged part of CLR, the code whoever has references to managed objects is responsible&amp;nbsp;to report all references itself. A GCFrame serves for this purpose. If an unmanaged method pushes a GCFrame to stack, the frame will report the protected reference&amp;nbsp;(the argument to GCPROTECT_BEGIN) during GC.&amp;nbsp;In our example, MethodBodyObj&amp;nbsp;is an object&amp;nbsp;reference so we set up a GCFrame for it. 
&lt;LI class=MsoNormal&gt;HELPER_METHOD_POLL: defined in vm/fcall.h. This macro is meant to do a GC poll in range of a HelperMethodFrame. GC poll is another complicated thing I don't want to talk here. Basically it allows GC to happen in another thread, without a poll another thread&amp;nbsp;that wants to perform a GC might be blocked, thus all managed threads in the application will be blocked.&lt;o:p&gt;&lt;/o:p&gt; 
&lt;LI class=MsoNormal&gt;OBJECTREFToObject: defined in vm/vars.hpp. It's used to take a pure object pointer out from an ObjectRef. An ObjectRef is a naked pointer in free build, but a wrapper with some very useful checking in debug build.&lt;o:p&gt;&lt;/o:p&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;&lt;STRONG&gt;The problem&lt;/STRONG&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;1. GC hole. In COMMember::GetMethodBody, a HELPER_METHOD_POLL is put after GCPROTECT_END. As I explained above, GCPROTECT_END will pop up the GC frame which is protecting the object reference, but HELPER_METHOD_POLL allows a GC to happen in another thread. So there are chances&amp;nbsp;(although very small) that after the GC frame is popped up, another thread performs a GC. In such a GC, MethodBodyObj won't be reported. So GC might not know the object referenced by&amp;nbsp;MethodBodyObj is still alive (if there's no other reference&amp;nbsp;to the object)&amp;nbsp;and collect it; or (if there are other references) GC might move the object but not update MethodBodyObj with the new address thus MethodBodyObj would hold a “stale” object pointer. Either case, COMMember::GetMethodBody will return a bogus object and the program might crash later in an unexpected way. We call this kind of errors “GC holes” in CLR.&amp;nbsp;They are hard to detect because GC is non-deterministic.&amp;nbsp;The funny thing is that nothing bad would happen in this method because HELPER_METHOD_POLL is actually defined as a no-op in this version:&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P&gt;&lt;EM&gt;&lt;SPAN&gt;// This is the fastest way to do a GC poll if you have already erected a HelperMethodFrame&lt;/SPAN&gt;&lt;/EM&gt;&lt;I&gt;&lt;SPAN&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;// #define HELPER_METHOD_POLL()&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { __helperframe.Poll(); INDEBUG(__fCallCheck.SetDidPoll()); }&lt;/SPAN&gt;&lt;/EM&gt;&lt;/SPAN&gt;&lt;/I&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;&lt;SPAN&gt;#define HELPER_METHOD_POLL()&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { }&lt;/SPAN&gt;&lt;/EM&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;I don't know why we&amp;nbsp;use an empty macro for HELPER_METHOD_POLL but I'm sure it's&amp;nbsp;supposed to be the version which is commented out. In later versions we may uncomment the above line to make HELPER_METHOD_POLL take effect. So although this FCall doesn't cause any trouble for now, it might later. The corrected version should be:&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P&gt;&lt;EM&gt;&lt;SPAN&gt;HELPER_METHOD_FRAME_BEGIN_RET_0();&lt;/SPAN&gt;&lt;/EM&gt;&lt;I&gt;&lt;SPAN&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;GCPROTECT_BEGIN(MethodBodyObj);&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;...&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;/EM&gt;&lt;/SPAN&gt;&lt;/I&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;&lt;SPAN&gt;HELPER_METHOD_POLL();&lt;/SPAN&gt;&lt;/EM&gt;&lt;I&gt;&lt;SPAN&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;GCPROTECT_END();&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;HELPER_METHOD_FRAME_END();&lt;/SPAN&gt;&lt;/EM&gt;&lt;/SPAN&gt;&lt;/I&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;2.&amp;nbsp;If you&amp;nbsp;dig deep into the code of&amp;nbsp; HELPER_METHOD_FRAME_BEGIN*, you will find that those macros do a GC poll themselves. So unless the FCall does some very time consuming work, there's no need for another poll.&amp;nbsp;Thus a&amp;nbsp;refined version of&amp;nbsp;our sample&amp;nbsp;would be:&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P&gt;&lt;EM&gt;&lt;SPAN&gt;HELPER_METHOD_FRAME_BEGIN_RET_0();&lt;/SPAN&gt;&lt;/EM&gt;&lt;I&gt;&lt;SPAN&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;GCPROTECT_BEGIN(MethodBodyObj);&lt;/SPAN&gt;&lt;/EM&gt;&lt;BR&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;...&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;/EM&gt;&lt;/SPAN&gt;&lt;/I&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;&lt;SPAN&gt;GCPROTECT_END();&lt;/SPAN&gt;&lt;/EM&gt;&lt;I&gt;&lt;SPAN&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;HELPER_METHOD_FRAME_END();&lt;/SPAN&gt;&lt;/EM&gt;&lt;/SPAN&gt;&lt;/I&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;3. Because setting up HelperMethodFrame usually means the code wants to allow GC,&amp;nbsp;for convenience we have&amp;nbsp;versions of HELPER_METHOD_FRAME_BEGIN* to protect&amp;nbsp;object references.&amp;nbsp;Then&amp;nbsp;a GCFrame is not needed. So the FCall could be written this way:&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P&gt;&lt;EM&gt;&lt;SPAN&gt;HELPER_METHOD_FRAME_BEGIN_RET_1(MethodBodyObj);&lt;/SPAN&gt;&lt;/EM&gt;&lt;I&gt;&lt;SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;EM&gt;&lt;SPAN&gt;...&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;/EM&gt;&lt;/SPAN&gt;&lt;/I&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;&lt;SPAN&gt;HELPER_METHOD_FRAME_END();&lt;/SPAN&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;I hope you already got a taste how FCall works in CLR by reading this blog. Actually most thing I talked here can be found in comments at the beginning of vm/fcall.h. And if you want to see more examples of FCall, just search FCIMPL in vm directory, you will get plenty of them. Then you will see how CLR build a&amp;nbsp;beautiful&amp;nbsp;object-oriented world&amp;nbsp;by the old fashion and kinda dirty way.&lt;SPAN&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;This posting is provided "AS IS" with no warranties, and confers no rights.&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=69906" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/yunjin/archive/tags/Rotor+code+explanation/default.aspx">Rotor code explanation</category><category domain="http://blogs.msdn.com/yunjin/archive/tags/CLR+internal+and+Misc/default.aspx">CLR internal and Misc</category></item></channel></rss>