6

有时用户想要安排大量的计时器,并且不想管理对这些计时器的引用。
如果用户不引用计时器,则计时器可能会在执行之前由 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

问题是,如果我不处置计时器,为什么会出现内存泄漏?

4

2 回答 2

10

由计时器本身创建的aTimer 保持活动状态。GCHandle这可以使用 .net 内存分析器进行测试。反过来Timer,这将使代表保持活力,然后让其他人保持活力。

AGCHandle是一种特殊的对象,可用于“欺骗”垃圾收集器以使无法访问的对象保持活动状态。

您实际上可以在没有探查器的情况下使用以下方法进行测试:

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);
于 2012-11-03T03:30:26.037 回答
4

TimersComponents。因此,Dispose当你完成它们时,你必须打电话。

文档中

组件应通过调用其Dispose方法显式释放资源,而无需等待通过隐式调用Finalize方法进行自动内存管理。当一个Container被释放时,该Container内的所有组件也会被释放。

“当一个容器被释放时,容器内的所有组件也被释放”部​​分。可以在 Form 的 Dispose 方法中看到它调用时:

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

因此,除非将计时器添加到组件中,否则不要期望计时器与 Form 一起处置。

更新您的评论:
计时器具有指向非托管代码(操作系统的计时器 API)的指针,因此在不再需要这些代码之前它无法释放。除非首先调用 dispose 或程序正在退出,否则终结器不会在对象上运行。这是因为这些当前对非托管代码的引用。

据我了解,处置模型假设可以加快程序关闭速度(因为运行时可以在停机期间收集垃圾),同时仍允许执行非托管代码。如果您执行大量 ddl 导入,您将开始了解系统为什么会这样工作。

还应该注意的是,文档表明您可能无法从对象的终结器访问托管对象。StreamWriter 就是一个例子。我个人认为这是一个任意规则,但它确实存在,因此需要 dispose 系统。

无论哪种方式,如果你使用了实现 iDisposable 接口的东西,你应该总是在用完后将其丢弃。这样,您将获得更好(更一致)的结果。

于 2012-11-03T01:27:42.960 回答