为什么Forms.Timer不会被垃圾回收

Forty2 2014-10-23 11:35:31
加精
其实标题有误,应该是“为什么Winform的Timer启动以后,不会被垃圾回收”。
该贴是分享贴,将通过一步步的试验,来解释Timer不被回收的原因。

一、垃圾回收的条件

我们知道,DotNet的垃圾回收很聪明,它将回收不再使用的对象。这里的‘不再使用’的具体逻辑,可以参考微软的文档,但简单的说,条件就是‘没有人引用’。
现在,创建一个WinForm的项目(不是Web或WPF),并拖一个按钮到设计器窗口上,在设计器窗口上双击按钮以生成如下代码,作为试验的起点:

using System;
using System.Windows.Forms;
using System.Reflection;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
}
}
}


当GC回收一个对象时,如果该对象有终结函数(Finerlizer),那么GC将调用终结函数。
因此,我们可以写一个终结函数来观察对象回收。

现在,在Form1后面,添加一个MyTimer的类,它将继承System.Windows.Forms.Timer。其中的~MyTimer()就是终结函数得写法。

public class MyTimer : System.Windows.Forms.Timer
{
~MyTimer()
{
MessageBox.Show("MyTimer 销毁了");
}
}


在InitializeComponent();后面,创建一个‘临时’的MyTimer实例:
public Form1()
{
InitializeComponent();

System.Windows.Forms.Timer timer = new MyTimer() { Interval = 1000 };
timer.Tick += delegate { this.Text = DateTime.Now.Second.ToString(); };

}

现在,我们在button1_Click中添加代码,来提早启动垃圾回收,看看是否timer实例被回收了:
private void button1_Click(object sender, EventArgs e)
{

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

}

编译项目并运行,按下Button按钮,应该可以观察到一个弹出消息框,说"MyTimer 销毁了"。
这里的timer没有被除了Form1()函数体外的任何人引用。因此,它就属于‘不再使用’的对象,就可以在垃圾回收中被清除。

二、启动后的Time不被回收

现在,在timer.Tick += ...这行的后面启动定时器
timer.Tick += delegate { this.Text = DateTime.Now.Second.ToString(); };
timer.Start();
编译运行,按下Button按钮,以观察到定时器促使窗口标题每秒变动,但没有销毁消息框弹出。

疑问是,timer还是本地变量,timer还是没有任何人引用,为什么它不会被垃圾回收呢?
答案是,微软故意阻止了timer被回收,具体做法是采用了GCHandle.Alloc来保护对象不被回收[备注1]。

现在,把timer.Start()注释掉,加上这么一句:
//timer.Start();
System.Runtime.InteropServices.GCHandle.Alloc(timer);
编译运行,点击按钮,看不到销毁消息框弹出。.GCHandle.Alloc起到了阻止垃圾回收的作用。

这里不讨论微软为什么要阻止timer被回收。但如果阅读微软的源代码,你会发现在Timer.Enable属性下,启用Timer的时候,用了GCHandle.Alloc,禁用的时候用了GCHandle.Free。[备注2]
而启动定时器,内部调用了Timer.Enable=true。
if (value)
{
if (timerWindow == null)
{
timerWindow = new TimerNativeWindow(this);
}

timerRoot = GCHandle.Alloc(this);
timerWindow.StartTimer(interval);
}
else
{
if (timerWindow != null)
{
timerWindow.StopTimer();
}

if (timerRoot.IsAllocated)
{
timerRoot.Free();
}
}

三、Timer导致的内存‘泄露’

由于运行中的Winform定时器不会被垃圾回收,它Tick事件下登记的回调函数也不会被回收。
如果回调函数是成员函数(相对于静态函数),那么该成员归属的对象,也不会被回收。这就造成了内存泄露。

public class A
{
string _name;
public A(string name)
{
_name = name;
Timer timer = new Timer() { Interval = 10000000 };
timer.Tick += delegate { MessageBox.Show(_name); };
timer.Start();
}
}
void Test()
{
A a = new A("实例a");
a = new A("实例b");
a = null; // 两个实例都不会得到回收,造成内存泄露
}


四、结论

Winform定时器的生命周期应该妥善管理。由于它是Component,实现了IDisposable,我们应该利用IDisposable来避免内存的泄露。即,当我们不用定时器时,要显式地停止它或销毁它。这里可以使用timer.Stop();或者timer.Dispose。

WPF下的DispatherTimer也有同样的问题,只不过它是被Dispatcher.CurrentDispatcher所引用,从而不被回收。



备注1: (GCHandle.Alloc 方法)
http://msdn.microsoft.com/zh-cn/library/vstudio/a95009h1%28v=vs.100%29.aspx

备注2: (Timer.cs源代码)
http://referencesource.microsoft.com/#System.Windows.Forms/ndp/fx/src/winforms/Managed/System/WinForms/Timer.cs







...全文
2352 1 收藏 51
写回复
51 条回复
why123qaz 2015年04月27日
留言,学习。
回复 点赞
oKaitouKidd 2015年01月22日
引用 4 楼 donglei88188 的回复:
写得非常好。给推荐下。 以前真的没有深入思考这个问题,Forms下的Timer很少被单独用。
回复 点赞
默默忧伤 2014年11月03日
学习一下!
回复 点赞
xusir98 2014年11月02日
回复 点赞
ikesi-X 2014年10月31日
这个我也不知道
回复 点赞
qq_22674375 2014年10月29日
很不错的样子,学习一下
回复 点赞
真相重于对错 2014年10月29日
如果lz有在非托管环境下的编程经验,就应该清楚再非托管情况下,所有的资源需要你自己完全的掌握,而不能有一个框架来替代你。 timer包含有非托管资源,所以他不能够被托管所完全管理,这就是原因
回复 点赞
rayyu1989 2014年10月29日
涨姿势了
回复 点赞
qq_22643863 2014年10月28日
回复 点赞
IE11下面经常卡到爆 2014年10月27日
Container中放的是Component
回复 点赞
IE11下面经常卡到爆 2014年10月27日
引用 5 楼 phommy 的回复:
thread里的timer没有这个问题 至于forms里的timer,微软的窗体设计器创建的timer,都会生成手动释放timer的代码(利用System.ComponentModel.Container),就不会泄漏内存了。如果是手动new的,要考虑释放问题
+1 Container跟Control类似,窗口关闭时,会自动释放子级
回复 点赞
xieyu1995 2014年10月27日
我是狗屎
回复 点赞
LiuCen8821 2014年10月27日
hehehe
回复 点赞
FBY19941216 2014年10月27日
因为它牛逼呀~~
回复 点赞
zzt1993127 2014年10月27日
可能是资源管理器的问题吧
回复 点赞
hpygzhx520 2014年10月27日
很厉害的样子,学习一下
回复 点赞
wangnaisheng 2014年10月26日
回复 点赞
LRRdy 2014年10月26日
感谢楼主分享
回复 点赞
oegnz 2014年10月26日
回复 点赞
luckyangman 2014年10月25日
回复 点赞
发动态
发帖子
C#
创建于2007-09-28

8.5w+

社区成员

64.0w+

社区内容

.NET技术 C#
社区公告
暂无公告