内存泄漏,如果没有明确停止Timer

时间:2017-11-06 10:59:10

标签: c# memory-leaks timer garbage-collection

如果我创建/订阅并启动它,System.Timers.Timer实例在我的应用程序中创建了哪些引用?

以下是我所知道的参考资料:

  1. 引用我们存储在某个变量中的计时器实例。
  2. 订阅Elapsed事件
  3. 时的隐含引用

    我假设在我们的Start计时器时必须创建一些额外的引用。但他们是什么?

    以下是我尝试过的一段代码:

    class Program
    {
        static void Main(string[] args)
        {
            var timers = new List<Timer>();
    
            Console.WriteLine("Timers to create/start and subscribe: ");
    
            // step 1
            var count = 1000000;
            for (int i = 0; i < count; i++)
            {
                var timer = new Timer(100000);
                timers.Add(timer);
    
                // step 2
                timer.Elapsed += Callback;
                timer.Start();
            }
    
            // step 3: 7mb -> 240mb
            Console.WriteLine("All timers subscribed and started");
    
            Task.Delay(TimeSpan.FromSeconds(15)).Wait();
    
            // step 4
            timers.ForEach(t => t.Elapsed -= Callback);
            Console.WriteLine("All timers unsubscribed");
    
            // step 5: uncomment next two lines to have timers GC collected.
            // timers.ForEach(t => t.Stop());
            // Console.WriteLine("All timers stopped");
    
            // step 6
            timers.Clear();
            Console.WriteLine("Collection cleared");
    
            // step 7
            do
            {
                Console.WriteLine("Press Enter to GC.Collect");
                Console.ReadLine();
                GC.Collect();
                Console.WriteLine("GC Collected");
            }
            while (true);
        }
    
        private static void Callback(object sender, ElapsedEventArgs e)
        {
            Console.WriteLine("callback");
        }
    }
    

    这是我在这里做的事情:

    1. 创建一百万个Timer个实例并将其存储到List
    2. 对于每个计时器:订阅传递给它的Elapsed事件静态方法参考,然后调用Start
    3. 一旦完成,我的应用程序在内存中从7mb增加到240mb。
    4. 在此之后,我取消订阅所有计时器Elapsed事件,从而杀死隐式事件订阅引用。
    5. 可选:停止所有计时器。
    6. 清除计时器列表以删除对其实例的引用。
    7. GC.Collect释放为计时器分配的内存。
    8. 如果Step 5被执行(计时器被停止),那么在GC.Collect步骤我可以看到我的应用程序占用的内存从240mb减少到大约20mb,这意味着Timers被正确回收。

      如果省略Step 5,则在GC.Collect步骤内存使用率保持在240mb级别,无论我等待多长时间并强制GC收集。这实际上意味着计时器实例保留在内存中并且无法收集。

      根据我所知道GC的工作方式,如果无法从根目录访问我的计时器,则应该从内存中清除它们(意味着没有引用)。

      我是否可以在没有明确调用timer.Stop()的情况下消除我的计时器的任何其他引用?

      在不调用Stop方法时是否还有其他方法可以防止内存泄漏?

0 个答案:

没有答案