2011-08-08 68 views
1

我有這個示例代碼,我試圖找出發生了什麼。使用線程時手柄越來越長 - 爲什麼?

private static AutoResetEvent autoEvent = new AutoResetEvent(false); 
    private static Thread t1; 
    static void DoSomething() 
    { 
     Console.WriteLine("Main starting."); 

     ThreadPool.QueueUserWorkItem(
      WorkMethod, autoEvent); 

     // Wait for work method to signal. 
     autoEvent.WaitOne(); 

     // trying out does resource cleanup by using dispose and null where possible 
     autoEvent.SafeWaitHandle.Dispose(); 
     t1 = null; 
     autoEvent = null; 
     autoEvent = new AutoResetEvent(false); 
     Console.WriteLine("Work method signaled.\nMain ending."); 
    } 

    static Action messageTarget; 

    static void WorkMethod(object stateInfo) 
    { 
     Console.WriteLine("Work starting."); 
      // This line is going to change 
      messageTarget = delegate() 
       { 
        Thread.Sleep(new Random().Next(100, 2000)); 
       }; 

     // Signal that work is finished. 
     Console.WriteLine("Work ending."); 
     ((AutoResetEvent)stateInfo).Set(); 
    } 

這工作得很好,並在循環100循環(使用TestApi的內存快照的句柄計數)後創建7個句柄。

現在有趣的現象是: 當我換委託在一個線程

 t1 = new Thread 
      (
      delegate() 
       { 
        Thread.Sleep(new Random().Next(100, 2000)); 
       }); 
     t1.Start(); 

應用程序完成大約295手柄!

我聽說.net框架與線程和清理資源很差,這是正確的嗎?當應用程序結束時,有可能某些線程仍在後臺運行,但確定這有點極端行爲?

我的問題是如何導致如此高的手數? (請注意,這是模擬的一些行爲在其他應用程序,並不意味着生產,而是要理解爲什麼句柄計數如此大幅度的增長當使用線程)用的Thread.join

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Threading; 
using System.Windows.Forms; 
using Microsoft.Test.LeakDetection; 

namespace FaultDetection 
{ 
    public partial class Form1 : Form 
    { 
     private Process process; 

    public Form1() 
    { 
     InitializeComponent(); 

     foreach (Process clsProcess in Process.GetProcesses()) 
     { 

      if (clsProcess.ProcessName.Contains("FaultDetection")) 
      { 
       //if the process is found to be running then we 
       //return a true 
       process = clsProcess; 
      } 
     } 

     MemorySnapshot s1; 
     if (process != null) 
     { 
      s1 = MemorySnapshot.FromProcess(process.Id); 

      for (int i = 0; i < 100; i++) 
      { 
       DoSomething(); 
       MemorySnapshot s2 = MemorySnapshot.FromProcess(process.Id); 

       // Compare the two memory snapshots and generate a diff. 
       // Then display the diff to the console. 
       MemorySnapshot diff = s2.CompareTo(s1); 

       Console.WriteLine("\tHandle Count: {0}", diff.HandleCount); 
       label1.Text = "Handle Count: "+ diff.HandleCount + "\n"; 
      } 
     } 
    } 

    private static AutoResetEvent autoEvent = new AutoResetEvent(false); 
    private static Thread t1; 
    private static List<Thread> threadReferences; 

    static void DoSomething() 
    { 
     Console.WriteLine("Main starting."); 

     ThreadPool.QueueUserWorkItem(
      WorkMethod, autoEvent); 

     // Wait for work method to signal. 
     autoEvent.WaitOne(); 

     t1.Join(); 
     autoEvent.SafeWaitHandle.Dispose(); 
     t1 = null; 
     autoEvent = null; 
     autoEvent = new AutoResetEvent(false); 
     Console.WriteLine("Work method signaled.\nMain ending."); 
    } 

    static Action messageTarget; 

    static void WorkMethod(object stateInfo) 
    { 
     Console.WriteLine("Work starting."); 
     t1 = new Thread 
      (
      delegate() 
       { 
        Thread.Sleep(new Random().Next(100, 2000)); 
       }); 
     t1.Start(); 
     //messageTarget = delegate() { Thread.Sleep(new Random().Next(100, 2000)); }; 

     // Signal that work is finished. 
     Console.WriteLine("Work ending."); 
     ((AutoResetEvent)stateInfo).Set(); 
    } 
} 

解決方案}

+0

在進行測量之前,請確保所有對象都已正確垃圾收集:'GC.Collect(); GC.WaitForPendingFinalizers();'然後多少個額外的句柄?如果你運行循環1000次,你會得到更多的句柄? –

+0

在40個標記周圍仍然居於頂峯,對於1000個週期,它圍繞着35-40個手柄並保持在那裏 – oliveromahony

回答

2

首先,.Net框架在線程和清理資源方面並不差。不知道你在哪裏聽到這個,一個資源的鏈接會很好。

有以下代碼有點麻煩:

  1. 如果您一邊喊DoSomething,你爲什麼要部署和創建新AutoResetEvent?您可以重新使用該實例並在循環結束時進行清理。

  2. messageTarget未在不直接使用Thread的版本中使用。

  3. 在直接使用Thread的版本中,您正在每個循環中創建並啓動一個新的Thread - 爲什麼?你將最終得到一堆正在運行的線程。由於沒有任何事情等待他們完成,他們都很可能在循環結束時運行。最終他們的睡眠時間將結束,他們將退出,但在第一次線程睡眠結束之前,您的循環將完成。

我懷疑句柄與您創建的正在運行的線程有關。您需要等待您的線程完成,例如使用Thread.Join。你的例子並沒有真正展示對線程有用的任何東西,它只是創造了很多。

更新

在回答你的問題的更新,通常你會使用ThreadPool,或TPL(使用線程池引擎蓋下),而不是直接創建線程。線程池在那裏是因爲他們有效地管理線程資源。

如果您直接創建Thread,可能會分配一些句柄(我相信CLR可以自由地重新使用某些線程資源,因此圖片很複雜)。

+0

我應該說我試圖在我們的應用程序中模擬一些行爲,每次創建一個新的線程是看什麼影響它對應用程序的處理次數。創建一個新的AutoResetEvent而不是重用是我留下代碼的最後一個狀態。我確實嘗試了這一點,但似乎並沒有改變句柄計數的結果。 – oliveromahony

+0

@oliveromahony好吧,如果你等待所有的線程在你的循環之後退出,你還會看到很多句柄嗎?目前,您並未在所有已創建的線程上同步等待。 –

+0

這樣做的句柄數仍然在40分左右,遠遠大於沒有線程的原始7句柄。 – oliveromahony

0

如果您使用線程池,就像您的原始代碼一樣,線程池將自動限制您要創建的線程和事件的數量。 如果明確創建線程,則BCL將執行所要求的操作:它將創建所有線程。每個線程都會創建一個句柄,當你不再使用線程對象時它將被關閉(即,當它完成運行時,我的猜測是你必須加入線程才能告訴CLR你沒有需要手柄了,但這只是一個猜測)。

+0

兩種情況下的代碼是相同的,唯一不同的是代理行 – oliveromahony

+0

是的,很明顯。我的觀點是,使用線程池排隊一個wark項目不會總是產生一個新的線程(及其關聯的句柄)。相反,創建一個新線程肯定會(正如chibacity指出的那樣,託管線程並不總是與本機線程一一對應)。 –

+0

您可以嘗試使用線程池嗎?你在這種情況下看到了多少把手?只需更換新的線程...以對QueueUserWorkItem的調用開始。 –