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

 更新时间:2020年02月12日 11:32:52   作者:丹枫无迹  
这篇文章主要介绍了C# 定时器保活机制引起的内存泄露问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

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 的引用,而是直接引用回调委托。

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

相关文章

  • C#中DataTable 转换为 Json的方法汇总(三种方法)

    C#中DataTable 转换为 Json的方法汇总(三种方法)

    JavaScript Object Notation (Json)是一种轻量级的数据交换格式,下面小编给大家介绍三种方法实现DataTable转换成 Json 对象,感兴趣的朋友一起看看吧
    2016-11-11
  • C# 中使用NModbus4通信库执行写操作

    C# 中使用NModbus4通信库执行写操作

    在C#中NModbus4库提供了一个方便的方式来与支持Modbus协议的设备进行交互,本文就来介绍了使用NModbus4通信库执行写操作,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • C#中AutoResetEvent控制线程用法小结

    C#中AutoResetEvent控制线程用法小结

    本文主要来自一道面试题,由于之前对AutoResetEvent的概念比较模糊,面试题题目很简洁:两个线程交替打印0~100的奇偶数,你可以先动手试试,我主要是尝试在一个方法里面完成这个任务,需要的朋友可以参考下
    2022-07-07
  • C# 关于AppDomain的一些总结

    C# 关于AppDomain的一些总结

    这篇文章主要介绍了C# 关于AppDomain的一些总结,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下
    2021-02-02
  • C#正则表达式实用大全(建议收藏!)

    C#正则表达式实用大全(建议收藏!)

    正则表达式是处理字符串的强大工具,拥有独特的语法和独立的处理引擎,下面这篇文章主要给大家介绍了关于C#正则表达式实用大全的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-03-03
  • C#对集合进行排序

    C#对集合进行排序

    这篇文章介绍了C#对集合进行排序的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • C#并行编程Task类用法介绍

    C#并行编程Task类用法介绍

    这篇文章介绍了C#并行编程Task类的用法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • WPF 自定义雷达图开发实例教程

    WPF 自定义雷达图开发实例教程

    这篇文章主要介绍了WPF 自定义雷达图开发实例教程,本文介绍的非常详细,具有参考借鉴价值,需要的朋友可以参考下
    2016-09-09
  • C#飞行棋小程序设计分析

    C#飞行棋小程序设计分析

    这篇文章主要为大家设计分析了C#飞行棋小程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • 详解C#中委托的概念与使用

    详解C#中委托的概念与使用

    委托这个名字取的神乎其神的,但实质是函数式编程,把函数作为参数传递给另一个参数。这篇文章主要为大家介绍一下C#中委托的概念与使用,需要的可以参考一下
    2023-02-02

最新评论