有时,用户希望安排大量计时器,并且不希望管理对这些计时器的引用。
如果用户没有引用计时器,则可以在GC执行之前收集计时器。
我创建了Timers类作为新创建的计时器的占位符:
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
集合中删除计时器后,正在控制对Timer的委托的引用。
问题是,如果我不处理计时器,为什么会出现内存泄漏?
答案 0 :(得分:9)
Timer
由计时器本身创建的GCHandle
保持活动状态。这可以使用.net内存分析器进行测试。反过来,Timer
将使代表保持活着,这将使其余的活着。
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);
答案 1 :(得分:4)
Timers
是Components
。因此,当您完成它们时,您必须致电Dispose
。
组件应通过调用其Dispose方法显式释放资源,而无需通过对Finalize方法的隐式调用等待自动内存管理。处置Container时,Container内的所有组件也会被处理掉。
部分“处置Container时,Container内的所有组件也会被处理掉。”当它调用时,可以在Form的Dispose方法中看到:
if (disposing && (components != null))
{
components.Dispose();
}
因此,除非将计时器添加到组件中,否则不要指望将计时器与表单一起处理。
更新您的评论:
计时器具有指向非托管代码(OS的计时器API)的指针,因此在不再需要它们之前无法进行处理。除非首先调用dispose或程序退出,否则终结器不会在对象上运行。这是因为这些当前对非托管代码的引用。
据我所知,dispose模型假设加速程序关闭(因为运行时可以在停机期间收集垃圾),同时仍允许执行非托管代码。如果您执行大量的ddl导入,您将开始了解系统的工作原理。
还应注意,文档表明您可能无法从对象的终结器访问托管对象。一个例子是StreamWriter。我个人认为这是一个武断的规则,但确实存在,因此需要配置系统。
无论哪种方式,如果您使用实现iDisposable接口的东西,您应该在完成后将其丢弃。通过这种方式,您将获得更好(更一致)的结果。