定时器可以自动收集垃圾吗?

时间:2013-08-08 21:48:18

标签: c# multithreading timer garbage-collection

当您使用TimerThread只会在程序的整个生命周期内运行时,您是否需要保留对它们的引用以防止它们被垃圾回收?

请撇开以下程序可以将timer作为类中的静态变量这一事实,这只是一个展示问题的玩具示例。

public class Program
{
    static void Main(string[] args)
    {
        CreateTimer();

        Console.ReadLine();
    }

    private static void CreateTimer()
    {
        var program = new Program();

        var timer = new Timer();
        timer.Elapsed += program.TimerElapsed;
        timer.Interval = 30000;
        timer.AutoReset = false;
        timer.Enabled = true;
    }

    private void TimerElapsed(object sender, ElapsedEventArgs e)
    {
        var timerCast = (Timer)sender;

        Console.WriteLine("Timer fired at in thread {0}", GetCurrentThreadId());

        timerCast.Enabled = true;
    }

    ~Program()
    {
        Console.WriteLine("Program Finalized");
    }

    [DllImport("kernel32.dll")]
    static extern uint GetCurrentThreadId();
}

上述示例中是否可以收集计时器?我跑了一段时间,我从来没有得到异常,也没有一条消息说~Program()被调用。

更新:我从this questionthanks sethcran)发现CLR会跟踪线程,但我仍然希望得到有关计时器的答案。

3 个答案:

答案 0 :(得分:43)

如果您没有在某处存储对它的引用,那么这只是System.Threading.Timer类的一个问题。它有几个构造函数重载,采用 state 对象的重载很重要。 CLR关注该状态对象。只要在某处引用它,CLR就会将计时器保留在其计时器队列中,并且计时器对象不会被垃圾收集。大多数程序员都不会使用那个状态对象,MSDN文章肯定不会解释它的作用。

System.Timers.Timer是System.Threading.Timer类的包装器,使其更易于使用。特别是,只要启用了计时器,它就会使用该状态对象并保持对它的引用。

请注意,在您的情况下,计时器的Enabled属性在进入Elapsed事件处理程序时为false,因为您有AutoReset = false。因此,一旦计时器进入您的事件处理程序,它就有资格收集。但是,您可以通过引用 sender 参数来避免麻烦,需要将Enabled设置为true。这使得抖动报告成为参考,因此您没有问题。

小心Elapsed事件处理程序。在没有诊断的情况下,将吞下该方法内引发的任何异常。这也意味着您不会将Enabled设置为true。您必须使用try / catch来做一些合理的事情。如果你不打算故意结束你的程序,至少你需要让你的主程序知道某些东西不再起作用了。在finally子句中放置Enabled = true可以避免收集计时器垃圾,但是可能会让程序一次又一次地抛出异常。

答案 1 :(得分:2)

将此代码添加到程序并运行它。你会看到没有收集计时器。

    private void DoStuff()
    {
        CreateTimer();
        Console.WriteLine("Timer started");
        int count = 0;
        for (int x = 0; x < 1000000; ++x)
        {
            string s = new string("just trying to exercise the garbage collector".Reverse().ToArray());
            count += s.Length;
        }
        Console.WriteLine(count);
        Console.Write("Press Enter when done:");
        Console.ReadLine();
    }

    private void Ticktock(object s, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine("Ticktock");
    }

    private void CreateTimer()
    {
        System.Timers.Timer t = new System.Timers.Timer(); // Timer(Ticktock, null, 1000, 1000);
        t.Elapsed += Ticktock;
        t.Interval = 1000;
        t.AutoReset = true;
        t.Enabled = true;
    }

因此,您的问题的答案似乎是计时器不符合收集条件,如果您没有保留对它的引用,则不会收集计时器。

有趣的是,如果您使用System.Threading.Timer运行相同的测试,您会发现 收集了

答案 2 :(得分:2)

让我们进行一个实验:

private static void UnderTest() {
  // Timer is a local varibale; its callback is local as well
  System.Threading.Timer timer = new System.Threading.Timer(
    (s) => { MessageBox.Show("Timer!"); }, 
     null, 
     1000, 
     1000);
}

...

// Let's perform Garbage Colelction manually: 
// we don't want any surprises 
// (e.g. system starting collection in the middle of UnderTest() execution)
GC.Collect(2, GCCollectionMode.Forced);

UnderTest();

// To delay garbage collection
// Thread.Sleep(1500);

// To perform Garbage Collection
// GC.Collect(2, GCCollectionMode.Forced);

到目前为止

  • 如果我们同时评论Thread.Sleep(1500);GC.Collect(2, GCCollectionMode.Forced);,我们会看到消息框出现:计时器正在工作
  • 如果我们取消注释 GC.Collect(2, GCCollectionMode.Forced);,将不会显示 :计时器开始计时,而不是被收集
  • 如果我们同时取消Thread.Sleep(1500);GC.Collect(2, GCCollectionMode.Forced);的注释,则将显示一个单个消息框:计时器启动,关闭一条消息框,然后收集计时器

所以Timer和其他对象实例一样被收集