当前位置: 移动技术网 > IT编程>开发语言>c# > C# 定时器保活机制引起的内存泄露问题解决

C# 定时器保活机制引起的内存泄露问题解决

2020年05月10日  | 移动技术网IT编程  | 我要评论

c# 中有三种定时器,system.windows.forms 中的定时器和 system.timers.timer 的工作方式是完全一样的,所以,这里我们仅讨论 system.timers.timer 和 system.threading.timer

1、定时器保活

先来看一个例子:

class program
{
  static void main(string[] args)
  {
    start();

    gc.collect();
    read();
  }

  static void start()
  {
    foo f = new foo();
    system.threading.thread.sleep(5_000);
  }
}

public class foo
{
  system.timers.timer _timer;

  public foo()
  {
    _timer = new system.timers.timer(1000);
    _timer.elapsed += timer_elapsed;
    _timer.start();
  }

  private void timer_elapsed(object sender, system.timers.elapsedeventargs e)
  {
    writeline("system.timers.timer elapsed.");
  }
  
  ~foo()
  {
    writeline("---------- end ----------");
  }
}

运行结果如下:

system.timers.timer elapsed.
system.timers.timer elapsed.
system.timers.timer elapsed.
system.timers.timer elapsed.
system.timers.timer elapsed.
system.timers.timer elapsed.
system.timers.timer elapsed.
...

在 start 方法结束后,foo 实例已经失去了作用域,按理说应该被回收,但实际并没有(因为析构函数没有执行,所以肯定实例未被回收)。

这就是定时器的 保活机制,因为定时器需要执行 timer_elapsed 方法,而该方法属于 foo 实例,所以 foo 实例被保活了。

但多数时候这并不是我们想要的结果,这种结果导致的结果就是 内存泄露,解决方案是:先将定时器 dispose。

public class foo : idisposable
{
  ...
  public void dispose()
  {
    _timer.dispose();
  }
}

一个很好的准则是:如果类中的任何字段所赋的对象实现了idisposable 接口,那么该类也应当实现 idisposable 接口。

在这个例子中,不止 dispose 方法,stop 方法和设置 autoreset = false,都能起到释放对象的目的。但是如果在 stop 方法之后又调用了 start 方法,那么对象依然会被保活,即便 stop 之后进行强制垃圾回收,也无法回收对象。

system.timers.timer system.threading.timer 的保活机制是类似的。

保活机制是由于定时器引用了实例中的方法,那么,如果定时器不引用实例中的方法呢?

2、不保活下 system.timers.timer 和 system.threading.timer 的差异

要消除定时器对实例方法的引用也很简单,将 timer_elapsed 方法改成 静态 的就好了。(静态方法属于类而非实例。)

改成静态方法后再次运行示例,结果如下:

system.timers.timer elapsed.
system.timers.timer elapsed.
system.timers.timer elapsed.
system.timers.timer elapsed.
---------- end ----------
system.timers.timer elapsed.
system.timers.timer elapsed.
system.timers.timer elapsed.
...

foo 实例是被销毁了(析构函数已运行,打印出了 end),但定时器还在执行,这是为什么呢?

这是因为,.net framework 会确保 system.timers.timer 的存活,即便其所属实例已经被销毁回收。

如果改成 system.threading.timer,又会如何?

class program
{
  static void main(string[] args)
  {
    start();

    gc.collect();
    read();
  }

  static void start()
  {
    foo2 f2 = new foo2();
    system.threading.thread.sleep(5_000);
  }
}

public class foo2
{
  system.threading.timer _timer;

  public foo2()
  {
    _timer = new system.threading.timer(timertick, null, 0, 1000);
  }

  static void timertick(object state)
  {
    writeline("system.threading.timer elapsed.");
  }

  ~foo2()
  {
    writeline("---------- end ----------");
  }
}

注意,这里的 timertick 方法是静态的。运行结果如下:

system.threading.timer elapsed.
system.threading.timer elapsed.
system.threading.timer elapsed.
system.threading.timer elapsed.
system.threading.timer elapsed.
---------- end ----------

可见,随着 foo2 实例销毁,_timer 也自动停止并销毁了。

这是因为,.net framework 不会保存激活 system.threading.timer 的引用,而是直接引用回调委托。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网