2012-11-03 44 views
5

有時用戶想要安排一大組定時器,並且不想管理對這些定時器的引用。
如果用戶沒有引用定時器,定時器可能會在GC執行之前被GC收集。
我創建的類定時器,以作爲新創建的計時器的佔位符:定時器代理周圍的內存泄漏

static class Timers 
{ 
    private static readonly ILog _logger = LogManager.GetLogger(typeof(Timers)); 

    private static readonly ConcurrentDictionary<Object, Timer> _timers = new ConcurrentDictionary<Object, Timer>(); 

    /// <summary> 
    /// Use this class in case you want someone to hold a reference to the timer. 
    /// Timer without someone referencing it will be collected by the GC even before execution. 
    /// </summary> 
    /// <param name="dueTime"></param> 
    /// <param name="action"></param> 
    internal static void ScheduleOnce(TimeSpan dueTime, Action action) 
    { 
     if (dueTime <= TimeSpan.Zero) 
     { 
      throw new ArgumentOutOfRangeException("dueTime", dueTime, "DueTime can only be greater than zero."); 
     } 
     Object obj = new Object(); 

     Timer timer = new Timer(state => 
     { 
      try 
      { 
       action(); 
      } 
      catch (Exception ex) 
      { 
       _logger.ErrorFormat("Exception while executing timer. ex: {0}", ex); 
      } 
      finally 
      { 
       Timer removedTimer; 
       if (!_timers.TryRemove(obj, out removedTimer)) 
       { 
        _logger.Error("Failed to remove timer from timers"); 
       } 
       else 
       { 
        removedTimer.Dispose(); 
       } 
      } 
     }); 
     if (!_timers.TryAdd(obj, timer)) 
     { 
      _logger.Error("Failed to add timer to timers"); 
     } 
     timer.Change(dueTime, TimeSpan.FromMilliseconds(-1)); 
    } 
} 

如果我不處置刪除計時器,它導致內存泄漏。
似乎某人在定時器從_timers集合中刪除之後持有對定時器委託的引用。

問題是,如果我不處理定時器,爲什麼會發生內存泄漏?

+0

也許我不明白你在問什麼,因爲它聽起來像你想知道爲什麼當你虐待它的東西行爲不端... –

+0

據我所知,該文件說組件應該處置。我仍然很好奇,什麼阻止GC收集計時器和給定的委託,而無需調用dispose方法。 –

回答

9

Timer由定時器本身創建的GCHandle保持活動狀態。這可以使用.net內存分析器進行測試。反過來Timer將保持代表活着,然後將其餘的活着。

A GCHandle是一種特殊類型的對象,可用於「欺騙」垃圾收集器以保持不可訪問的對象的活性。

其實你可以種,測試這種沒有使用分析:

var a = new ClassA(); 
var timer = new Timer(a.Exec); 

var refA = new WeakReference(a); 
var refTimer = new WeakReference(timer); 

a = null; 
timer = null; 

GC.Collect(); 
GC.WaitForPendingFinalizers(); 
GC.Collect(); 

Console.WriteLine(refA.IsAlive); 
Console.WriteLine(refTimer.IsAlive); 
+0

這很有趣。我想知道爲什麼System.Threading.Timer文檔說:「只要你使用一個Timer,你必須保留對它的引用。與任何託管對象一樣,Timer在沒有引用時會被垃圾收集。定時器仍然有效的事實並不妨礙它被收集。「我還注意到,如果定時器沒有引用,定時器可能不會被執行。 –

+0

看起來好像這是開發者打算工作的方式。如果我在.net 4.0或更高版本中運行我的測試程序,它似乎收集到的對象。 –

4

TimersComponents。因此,您必須在完成操作後致電Dispose

the documentation

一個組件應通過調用其Dispose方法明確地釋放資源,而通過對Finalize方法隱式調用等待自動內存管理。當處置Container時,Container內的所有組件也被處置。

的部分「當一個Container佈置,所述Container內的所有部件也設置」。可以在窗體的Dispose方法可以看出,當它調用:

if (disposing && (components != null)) 
{ 
    components.Dispose(); 
} 

所以不要指望定時器設置爲與形式,除非它們被添加到組件。

更新您的評論:
計時器有指向非託管代碼(操作系統的計時器API)的指針,所以它不能處理,直到不再需要這些代碼。終結器不會在對象上運行,除非首先調用dispose或程序正在退出。這是因爲這些當前引用非託管代碼。

從我所瞭解的處理模型是假設加快程序關閉(因爲運行時可能收集垃圾在停機時間),同時仍允許執行非託管代碼。如果您進行大量ddl導入,您將開始明白爲什麼系統按照它的方式工作。

還應該注意的是,文檔表明您可能無法訪問來自對象終結器的託管對象。這是一個StreamWriter的例子。我個人認爲這是一個武斷的規則,但它確實存在,因此需要處置系統。無論哪種方式,如果您使用的是實現iDisposable接口的東西,那麼您應該在處理完它後處置它。您將以這種方式獲得更好(更一致)的結果。

+0

對。但計時器將由GC收集,然後調用終結器,釋放委託和其他資源。我的問題是爲什麼在這種情況下需要Dispose方法。 –

+0

@OronNadiv計時器具有指向非託管代碼(操作系統的計時器API)的指針,因此在不再需要這些代碼之前不能進行處理。終結器不會在對象上運行,除非首先調用dispose,或者由於這些對非託管代碼的當前引用而退出程序。 – Trisped

+1

看來他正在使用'System.Threading.Timer',它不是'Component'。 –