通常我们需要定时执行一段任务的时候,我们就需要定时器,这时我们就可以使用c# System.Threading空间中的 Timer定时器;他是个异步定时器,时间到时每次都是在线程池中分配一个线程去执行任务。下面我们来看一个有趣的例子:

class Program
{
  static void Main(string[] args)
  {
    Timer timer = new Timer(TimerCallback,null,0,2000);
     
    Console.ReadLine();
  }

  private static void TimerCallback(object o)
  {
    Console.WriteLine("in TimerCallback method");
	
    GC.Collect();     
  }
}

当我们在debug模式下运行该段程序时,正如我们期盼的那样程序会每隔2秒钟执行该方法,打印出"in TimerCallback method”,而在release模式下执行的时候,只执行一次该方法,字符串只打印一次。

在这里我们在调用TimerCallback方法时,强制执行垃圾回收器,说明在release模式下,垃圾收集会回收Timer对象。那么为什么在debug模式下却能够运行能,这跟c#编译器的优化方式有关,在release模式下编译器做了相关的优化操作。而在debug模式下,timer对象的生成期是方法的结束,这样做也是为了调试的方便。要不然在调试时,我们执行到Timer timer = new Timer()后想看timer的值时,已经被垃圾回收器给回收了,这是我们不期望看到的结果。

C#的垃圾回收采用了reference tracking的算法,大概的意思是说在执行垃圾回收时,所有的对象都默认认为是可以被回收的,然后遍历所有的roots(指向reference type的对象,包括类成员变量,静态变量,函数参数,函数局部变量),把这个root指向的对象标记成不能被回收的。

回到我们的代码,当我们强制调用GC.Collect()时,这个时候我们的timer t已经是一个没有被指向的对象了,于是垃圾回收就把t给回收了。这和C++的对象析构不太一样,C++的对象需要在出了作用域之后析构函数才会被调用到。所以,即使我们没有显示的在这里调用GC.Collect(),但是我们不能确定什么时候CLR会调用GC,那个时候timer也就被回收了,总之,不能实现我们的意图。

正确的代码:

class Program
{
  static void Main(string[] args)
  {
    Timer timer = new Timer(TimerCallback,null,0,2000);
   
    Console.ReadLine();
    timer.Dispose();
  }

  private static void TimerCallback(object o)
  {
    Console.WriteLine("in TimerCallback method");

    GC.Collect();     
  }
}

这时不管是在release模式下还是debug模式下,都会每隔2秒钟调用我们的回调方法。

当然,我们也可以直接用using语句:

class Program
{
  static void Main(string[] args)
  {
    Using (Timer timer = new Timer(TimerCallback,null,0,2000))
    {
	  Console.ReadLine();
    }
  }

  private static void TimerCallback(object o)
  {
    Console.WriteLine("in TimerCallback method");

    GC.Collect();     
  }
}