<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://msmvps.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>the blog =&gt; anything goes : software debugging Windbg leaks</title><link>http://msmvps.com/blogs/senthil/archive/tags/software+debugging+Windbg+leaks/default.aspx</link><description>Tags: software debugging Windbg leaks</description><dc:language>en</dc:language><generator>CommunityServer 2008.5 SP2 (Build: 40407.4157)</generator><item><title>String.Empty versus "" - The real deal</title><link>http://msmvps.com/blogs/senthil/archive/2008/06/27/string-empty-versus-quot-quot.aspx</link><pubDate>Fri, 27 Jun 2008 13:36:00 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1638404</guid><dc:creator>Senthil</dc:creator><slash:comments>3</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/senthil/rsscomments.aspx?PostID=1638404</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/senthil/commentapi.aspx?PostID=1638404</wfw:comment><comments>http://msmvps.com/blogs/senthil/archive/2008/06/27/string-empty-versus-quot-quot.aspx#comments</comments><description>&lt;p&gt;This is one topic that keeps popping up every now and then. A lot of people seem to be curious about the performance implications of using one versus the other, and not unexpectedly, a lot of different answers come up.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;To settle it once for all, here&amp;#39;s a small program that uses both of them. &lt;br /&gt;&lt;/p&gt;
&lt;pre&gt;namespace ConsoleApplication&lt;br /&gt;{  &lt;br /&gt;    class Program&lt;br /&gt;    {&lt;br /&gt;        static void Main()&lt;br /&gt;        {&lt;br /&gt;            Method1();&lt;br /&gt;            Method2();&lt;br /&gt;            Console.ReadLine();&lt;br /&gt;            Method1();&lt;br /&gt;            Method2();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        [MethodImpl(MethodImplOptions.NoInlining)]&lt;br /&gt;        static void Method1()&lt;br /&gt;        {&lt;br /&gt;            string s = &amp;quot;&amp;quot;;&lt;br /&gt;            DoNothing(s); &lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        [MethodImpl(MethodImplOptions.NoInlining)]&lt;br /&gt;        static void Method2()&lt;br /&gt;        {&lt;br /&gt;            string s = string.Empty;&lt;br /&gt;            DoNothing(s);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        [MethodImpl(MethodImplOptions.NoInlining)]&lt;br /&gt;        static void DoNothing(string s)&lt;br /&gt;        {&lt;br /&gt;            Console.WriteLine(s);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;
&lt;p&gt;As you can see above, Method1 and Method2 call DoNothing, passing &amp;quot;&amp;quot; and string.Empty respectively. Now how do we compare these two? Easy, just look at the jitted code - after all, that&amp;#39;s what is going to run on the machine.&lt;/p&gt;&lt;p&gt;We&amp;#39;ll use Windbg to disassemble the code after the JITter did its job. And if you&amp;#39;re wondering why Method1 and Method2 have been called twice, now you know why - the second time around, we can look at the JITted code that was generated as part of the first time execution of each of those methods. &lt;/p&gt;&lt;p&gt;So breaking into the debugger at Console.ReadLine() and dumping the method descriptors for Program &lt;br /&gt;&lt;/p&gt;
&lt;pre&gt;0:003&amp;gt; !Name2EE Sample.exe!ConsoleApplication.Program&lt;br /&gt;...&lt;br /&gt;MethodTable: 002a3048&lt;br /&gt;&lt;br /&gt;0:003&amp;gt; !DumpMT -MD 002a3048&lt;br /&gt;...&lt;br /&gt;--------------------------------------&lt;br /&gt;   Entry MethodDesc      JIT Name&lt;br /&gt;...&lt;br /&gt;00860070   002a3020      JIT ConsoleApplication.Program.Main()&lt;br /&gt;008600a8   002a3028      JIT ConsoleApplication.Program.Method1()&lt;br /&gt;00860100   002a3030      JIT ConsoleApplication.Program.Method2()&lt;br /&gt;008600c8   002a3038      JIT ConsoleApplication.Program.DoNothing(System.String)&lt;br /&gt;002ac021   002a3040     NONE ConsoleApplication.Program..ctor()&lt;br /&gt;&lt;/pre&gt;
&lt;p&gt;shows the method descriptors for all the methods in that class. Now, using !u to disassemble code for Method1&lt;/p&gt;
&lt;pre&gt;0:003&amp;gt; !u 002a3028&lt;br /&gt;Normal JIT generated code&lt;br /&gt;ConsoleApplication.Program.Method1()&lt;br /&gt;Begin 008600a8, size d&lt;br /&gt;008600a8 8b0d3c30e402    mov     ecx,dword ptr ds:[2E4303Ch] (&amp;quot;&amp;quot;)&lt;br /&gt;008600ae ff158c302a00    call    dword ptr ds:[2A308Ch] (ConsoleApplication.Program.DoNothing(System.String), mdToken: 06000004)&lt;br /&gt;008600b4 c3              ret&lt;br /&gt;&lt;/pre&gt;
&lt;p&gt; and then for Method2&lt;/p&gt;
&lt;pre&gt;0:003&amp;gt; !u 002a3030&lt;br /&gt;Normal JIT generated code&lt;br /&gt;ConsoleApplication.Program.Method2()&lt;br /&gt;Begin 00860100, size c&lt;br /&gt;00860100 8b0d2c10e402    mov     ecx,dword ptr ds:[2E4102Ch] (&amp;quot;&amp;quot;)&lt;br /&gt;00860106 e8bdffffff      call    008600c8 (ConsoleApplication.Program.DoNothing(System.String), mdToken: 06000004)&lt;br /&gt;0086010b c3              ret&lt;br /&gt;&lt;/pre&gt;
&lt;p&gt;shows that, surprise surprise,&lt;b&gt; the generated assembly code is identical&lt;/b&gt;.&lt;b&gt; &lt;/b&gt;The only difference is the address referenced in the mov instruction.&lt;/p&gt;&lt;p&gt;So there you have it - the JITter generates the same code whether you use String.Empty or &amp;quot;&amp;quot;, and there should no difference in performance.&lt;/p&gt;&lt;p&gt;PS : It&amp;#39;s interesting to note that the two addresses are different, even though the string contents are the same. Address 2E4303Ch in the disassembly for Method1 is the address referring to an interned string object with the content &amp;quot;&amp;quot;. You can verify that by creating another string variable initialized to &amp;quot;&amp;quot; and disassembling that code - the same address (2E4303Ch) will be referenced there as well. What&amp;#39;s surprising is that the address referenced in Method2 is different - which means the string object referenced by string.Empty is not the interned object. Wonder why - maybe because it&amp;#39;s ngen&amp;#39;ned code? &lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1638404" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/senthil/archive/tags/software/default.aspx">software</category><category domain="http://msmvps.com/blogs/senthil/archive/tags/software+C_2300_+language/default.aspx">software C# language</category><category domain="http://msmvps.com/blogs/senthil/archive/tags/software+debugging+Windbg+leaks/default.aspx">software debugging Windbg leaks</category></item><item><title>The case of the leaking thread handles</title><link>http://msmvps.com/blogs/senthil/archive/2008/05/29/the-case-of-the-leaking-thread-handles.aspx</link><pubDate>Thu, 29 May 2008 18:15:00 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1628861</guid><dc:creator>Senthil</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/senthil/rsscomments.aspx?PostID=1628861</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/senthil/commentapi.aspx?PostID=1628861</wfw:comment><comments>http://msmvps.com/blogs/senthil/archive/2008/05/29/the-case-of-the-leaking-thread-handles.aspx#comments</comments><description>&lt;p&gt;This was one of the more challenging and interesting problems to debug in a while. When testing out code for an unrelated fix, I noticed that the application&amp;#39;s handle count, as seen in the Handles column in Task Manager, kept rising steadily.&lt;/p&gt;&lt;p&gt;WinDbg, showed that the app was leaking thread handles.&lt;/p&gt;&lt;pre&gt;0:003&amp;gt; !handle&lt;p&gt;1417 Handles&lt;br /&gt;Type&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;Count&lt;br /&gt;Event&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;452&lt;br /&gt;Section&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;8&lt;br /&gt;File&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;12&lt;br /&gt;Port&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;2&lt;br /&gt;Directory&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;3&lt;br /&gt;&lt;b&gt;Thread&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;916&lt;/b&gt;&lt;br /&gt;Desktop&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;1&lt;br /&gt;KeyedEvent&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;1 &lt;/p&gt;&lt;/pre&gt;&lt;p&gt;Half expecting the code to hold references to dead threads, I dumped Thread objects in the GC heap.&lt;/p&gt;&lt;pre&gt;0:003&amp;gt; !dumpheap -stat -type System.Threading.Thread&lt;p&gt;total 353 objects&lt;br /&gt;Statistics:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MT&amp;nbsp;&amp;nbsp;&amp;nbsp; Count&amp;nbsp;&amp;nbsp;&amp;nbsp; TotalSize Class Name&lt;br /&gt;790fe284&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 144 System.Threading.ThreadAbortException&lt;br /&gt;79124b74&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 32&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 640 System.Threading.ThreadHelper&lt;br /&gt;791249e8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 33&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1056 System.Threading.ThreadStart&lt;br /&gt;&lt;b&gt;790fe704&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 286&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16016 System.Threading.Thread&lt;/b&gt;&lt;br /&gt;Total 353 objects&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;No luck there. This was perplexing - I knew the app created a lot of threads that do some work and die, but the Thread class is the only way the app deals with them, so if it&amp;#39;s not Thread instances, who else was holding the handles open? &lt;/p&gt;&lt;p&gt;Realizing that barring a CLR bug, there must be some .NET object behind each handle, I dumped the entire GC heap, looking for types with the number of instances close to the number of handles. &lt;/p&gt;&lt;pre&gt;&amp;nbsp;0:003&amp;gt; !dumpheap -stat&lt;p&gt;...&lt;/p&gt;&lt;p&gt;7912d9bc&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 25&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 54264 System.Collections.Hashtable+bucket[]&lt;br /&gt;7b483294&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1098&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 57096 System.Windows.Forms.CreateParams&lt;br /&gt;&lt;b&gt;7b48193c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1098&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 61488 System.Windows.Forms.Control+ControlNativeWindow&lt;/b&gt;&lt;br /&gt;7912d8f8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 544&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 72264 System.Object[]&lt;br /&gt;&lt;b&gt;7b48398c&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 819&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 85176 System.Windows.Forms.Application+MarshalingControl&lt;/b&gt;&lt;br /&gt;&lt;b&gt;7b4835ec&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 819&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 108108 System.Windows.Forms.Application+ThreadContext&lt;/b&gt;&lt;br /&gt;0014c740&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 40&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 137064&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Free&lt;br /&gt;790fd8c4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 7016&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 421636 System.String&lt;br /&gt;Total 22730 objects&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;That number (819) of ThreadContext instances was pretty close to the number of open thread handles (916). Repeating the above exercise after letting the application run some more time confirmed the theory - the number of ThreadContext instances increased by almost the same extent as the number of open thread handles.&lt;/p&gt;&lt;p&gt;It should have been simple from now on - all that was left was to find the roots holding the ThreadContext instances and problem solved.&amp;nbsp; &lt;/p&gt;&lt;pre&gt;0:003&amp;gt; !dumpheap -mt 7b4835ec&lt;p&gt;...&amp;nbsp;&lt;/p&gt;&lt;p&gt;0145b550 7b4835ec&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 132&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;total 819 objects&lt;br /&gt;Statistics:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MT&amp;nbsp;&amp;nbsp;&amp;nbsp; Count&amp;nbsp;&amp;nbsp;&amp;nbsp; TotalSize Class Name&lt;br /&gt;7b4835ec&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 819&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 108108 System.Windows.Forms.Application+ThreadContext&lt;br /&gt;Total 819 objects&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;and then&lt;/p&gt;&lt;pre&gt;&amp;nbsp;0:003&amp;gt; !gcroot 0145b550&lt;p&gt;&amp;nbsp;Note: Roots found on stacks may be false positives. Run &amp;quot;!help gcroot&amp;quot; for&lt;br /&gt;more info.&lt;br /&gt;Scan Thread 0 OSTHread 34c0&lt;br /&gt;Scan Thread 2 OSTHread 34c8&lt;br /&gt;DOMAIN(001546E8):HANDLE(Pinned):9f13f0:Root:02373030(System.Object[])-&amp;gt;&lt;br /&gt;013720f4(System.Collections.Hashtable)-&amp;gt;&lt;br /&gt;0142486c(System.Collections.Hashtable+bucket[])&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;Here&amp;#39;s where the fun started. The main root was (System.Object[]), which indicated that the next object in the object graph (Hashtable) was a static member of some class. A search for static Hashtables in the application&amp;#39;s source code turned up nothing. The only possibility then was the BCL - some class there must be holding a static Hashtable of ThreadContexts.&lt;/p&gt;&lt;p&gt;I got a lucky break there - Reflector showed that ThreadContext class had a static Hashtable and that it&amp;#39;s constructor adds itself (this), to the hashtable. But who instantiates ThreadContexts? Reflector&amp;#39;s &amp;quot;Analyze&amp;quot; command turned up too many method flows to go through one by one, so I decided to do it the other way - set up a breakpoint on ThreadContext&amp;#39;s constructor and let the application run.&lt;/p&gt;&lt;pre&gt;0:003&amp;gt; DumpMT -MD 7b4835ec&lt;p&gt;&amp;nbsp;EEClass: 7b483400&lt;br /&gt;Module: 7b454000&lt;br /&gt;Name: System.Windows.Forms.Application+ThreadContext&lt;br /&gt;mdToken: 020001e4&amp;nbsp; (C:\WINDOWS\assembly\GAC_MSIL\System.Windows.Forms\2.0.0.0__b77a5c561934e089\System.Windows.Forms.dll)&lt;br /&gt;BaseSize: 0x84&lt;br /&gt;ComponentSize: 0x0&lt;br /&gt;Number of IFaces in IFaceMap: 1&lt;br /&gt;Slots in VTable: 64&lt;br /&gt;--------------------------------------&lt;br /&gt;MethodDesc Table&lt;br /&gt;&amp;nbsp;&amp;nbsp; Entry MethodDesc&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; JIT Name&lt;/p&gt;&lt;p&gt;...&lt;/p&gt;&lt;p&gt;7b664ad4&amp;nbsp;&amp;nbsp; 7b4a76b0&amp;nbsp;&amp;nbsp; PreJIT System.Windows.Forms.Application+ThreadContext..ctor()&lt;/p&gt;&lt;p&gt;&amp;nbsp;and then&lt;/p&gt;&lt;p&gt;0:003&amp;gt; !bpmd -md 7b4a76b0&amp;nbsp;&amp;nbsp; &lt;/p&gt;&lt;p&gt;&lt;br /&gt;MethodDesc = 7b4a76b0&lt;br /&gt;Setting breakpoint: bp 7B06B934 [System.Windows.Forms.Application+ThreadContext..ctor()]&lt;br /&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&amp;nbsp;When the breakpoint was hit, the stack looked like this&lt;/p&gt;&lt;pre&gt;&amp;nbsp;!CLRStack&amp;nbsp;&lt;p&gt;OS Thread Id: 0x2c90 (3)&lt;br /&gt;ESP&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; EIP&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;0106ea74 7b06b934 System.Windows.Forms.Application+ThreadContext..ctor()&lt;br /&gt;0106ea78 7b06b8cc System.Windows.Forms.Application+ThreadContext.FromCurrent()&lt;br /&gt;0106ea80 7b06b885 System.Windows.Forms.WindowsFormsSynchronizationContext..ctor()&lt;br /&gt;0106ea90 7b06b7b2 System.Windows.Forms.WindowsFormsSynchronizationContext.InstallIfNeeded()&lt;br /&gt;0106eabc 7b06a09b System.Windows.Forms.Control..ctor(Boolean)&lt;br /&gt;&lt;b&gt;0106eb30 7b068f75 System.Windows.Forms.Label..ctor()&lt;/b&gt;&lt;br /&gt;0106eb3c 00d5012b LanguageFeatures.Program.&amp;lt;Main&amp;gt;b__0()&lt;br /&gt;0106eb44 793b0d1f System.Threading.ThreadHelper.ThreadStart_Context(System.Object)&lt;br /&gt;0106eb4c 79373ecd System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)&lt;br /&gt;0106eb64 793b0c68 System.Threading.ThreadHelper.ThreadStart()&lt;br /&gt;0106ed8c 79e7c74b [GCFrame: 0106ed8c]&amp;nbsp; &lt;/p&gt;&lt;/pre&gt;&lt;p&gt;As the stack dump shows, the ThreadContext was created as part of System.Windows.Forms.Control&amp;#39;s constructor to set the synchronization context of the thread to WindowsForms. The problem was that created context didn&amp;#39;t die when the thread died. Some more poking around with Reflector showed that the instance is removed from static Hashtable when the thread receives a quit message (via Application.ExitThread, for example). The threads in the app were not pumping messages, so the ThreadContexts kept accumulating in the Hashtable, keeping the associated Thread handle open. Here&amp;#39;s some code that demonstrates the problem. &lt;/p&gt;
&lt;pre&gt;class Program
{
   static void Main(string[] args)
   {
      while (true)
      {
         new Thread(delegate() { new System.Windows.Forms.Label(); }).Start();
         Thread.Sleep(100);
      }
   }
}
&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Bottomline - don&amp;#39;t create controls from non-message pumping threads, especially if you create lots of threads over the lifetime of the application. &lt;/p&gt;&lt;p&gt;It seems obvious in retrospect, but in the application&amp;#39;s case, it was not using the control visually, so it didn&amp;#39;t really need WM_PAINT or the hundred other messages that a control needs to work. The fix was to move control creation to a GUI (message pumping) thread and then Invoke/BeginInvoke from the other threads. &lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1628861" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/senthil/archive/tags/software+debugging+Windbg+leaks/default.aspx">software debugging Windbg leaks</category></item></channel></rss>