计时器委托周围的内存泄漏

Oro*_*div 5 .net c# multithreading timer

有时,用户希望安排大量计时器,并且不希望管理对这些计时器的引用.
在用户没有引用计时器的情况下,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));
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我不处理已移除的计时器,则会导致内存泄漏.
似乎有人在从_timers集合中删除计时器后持有对Timer的委托的引用.

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

Jan*_*Vos 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);
Run Code Online (Sandbox Code Playgroud)