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

Sco*_*ain 24 c# multithreading garbage-collection timer

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

请放下以下程序可以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();
}
Run Code Online (Sandbox Code Playgroud)

计时器是否可以在上面的示例中收集?我跑了一段时间,我从来没有得到过例外,也没有一条消息说~Program()被叫.

更新:我从这个问题(感谢sethcran)中发现线程被CLR跟踪,但我仍然想要一个关于Timers的答案.

Han*_*ant 48

这只是System.Threading.Timer类的一个问题,如果你没有以其他方式存储对它的引用.它有几个构造函数重载,采用状态对象的重载很重要.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。特别喜欢您提到疯狂的异常压榨,这是我不使用`System.Timers.Timer`的主要原因。 (2认同)

Dmi*_*nko 5

我们来做个实验:

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

...

// Let's perform Garbage Collection 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);
Run Code Online (Sandbox Code Playgroud)

迄今为止

  • 如果我们保持评论Thread.Sleep(1500);GC.Collect(2, GCCollectionMode.Forced);我们会看到消息框出现:计时器正在工作
  • 如果我们取消注释, GC.Collect(2, GCCollectionMode.Forced);我们将看不到任何内容:计时器启动然后收集
  • 如果我们取消注释两者Thread.Sleep(1500);GC.Collect(2, GCCollectionMode.Forced);我们将看到一个消息框:计时器启动,关闭一个消息框,然后收集计时器

因此System.Threading.Timers 会像任何其他对象实例一样被收集。