跳出三界外,不在五行中的timer:
在使用timer的时候,发现timer类型对象的作用于比较奇特。一般而言,在函数内定义的变量,其作用域不超过函数,在函数结束的时候变量的生命周期就结束了,但是万事里总会有个一,普遍规律下总有那么一些例外的东西,比如说timer,其作用域就不会因函数的结束而结束。考察以下事件处理函数,
{
System.Timers.Timer t = new System.Timers.Timer();
t.Interval = 1000 * 2;
t.Elapsed += delegate
{
MessageBox.Show(System.DateTime.Now.ToString());
};
t.Start();
}
按完button1按钮后,timer持续运行,每隔约2秒就会弹出一个msgbox显示当前时间,当然,因为我们已经失去了t这个变量,表面上也就无法终止这个timer了(也许有办法终止吧)。对于这种现象,我推测是三种原因造成的。一,timer是对windows内核对象的包装,上面这段托管代码的底层调用了来自windows的一些内核对象,在超离作用域的时候,没有对内核对象进行相应的处理,当然这只是推测,我现在没有精力去证实。其二:委托原因。其三 :使用了多线程技术。
考察以下代码,
{
System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(delegate() {
for (int i = 0; i < 6; i++)
{
System.Threading.Thread.Sleep(3000);
MessageBox.Show("I'm in thread");
}
}));
t.Start();
MessageBox.Show("end of click");
}
以上两段代码,现象是类似的,我将其称之为游魂现象——看上变量已经死了,实际上依然存在,原理尚有待研究。
内存泄漏?
t在它的理论生命外依然存在——在整个程序的生命周期中都存在,而且在函数之外的任何地方都没有变量指向它引用的对象(内存),这中现象似乎是内存泄漏了。然而t内执行的是我所期望的代码,一直在为我服务着,一直在发挥着实质作用,我也不需要访问与控制它,因此没有造成内存浪费,不能称之为泄漏,因为它一直有效、有用。
小作用域长生命周期!合理而不合礼的现象!
t的持续执行是我期望的。我只需要一个变量启动定时器,以后不需要在任何地方干涉它,所以我需要一个作用域小的局部变量(如果使用全局变量会遇到重名、误修改等问题),根据程序的表现而看,在函数内声明的t可以正常运行,而且表现良好,不会被GC回收(使用GC.Collect();GC.WaitForPendingFinalizers();GC.Collect();测试过)。看起来很理想,即满足小作用域的要求,又满足持续运行的期望。然而事情并不美好,这种写法不合乎C#的“礼制”,它运行时的行为超出了C#约定的常规生命周期。
如何避免?
如果您确实不想让t超出function的生命周期继续存在,可以使用“显式资源回收”,在必要的地方使用调用t.Dispose,以下代码中的timer就不会“游魂”,该function有约20s的生命期,20s内msgbox会不断弹出,20s后就没有了。不这样也失去了timer的意义。
{
System.Timers.Timer t = new System.Timers.Timer();
t.Interval = 1000 * 2;
t.Elapsed += delegate
{
MessageBox.Show(System.DateTime.Now.ToString());
};
t.Start();
System.Threading.Thread.Sleep(1000 * 20);
t.Stop();
}
using的法力?
1.以下代码用于测试了using块中的表现:
ui线程被阻塞20s,观察发现20s内timer没有执行,可以看出t在离开using块后被回收了。
{
using (System.Timers.Timer t = new System.Timers.Timer())
{
t.Interval = 1000 * 1;
t.Elapsed += delegate
{
MessageBox.Show(System.DateTime.Now.ToString());
};
t.Start();
}
System.Threading.Thread.Sleep(1000 * 20);
}
2.再看以下代码
我给了using约5s的生命周期,结果msgbox在5s内弹出来几次,以后就不弹了,进一步证实using的威力,5s后回收了t引用的资源。
{
using (System.Timers.Timer t = new System.Timers.Timer())
{
t.Interval = 1000 * 1;
t.Elapsed += delegate
{
MessageBox.Show(System.DateTime.Now.ToString());
};
t.Start();
System.Threading.Thread.Sleep(1000 * 5);
}
}
GC又如何?
强制GC回收一下资源,结果是msgbox依旧顽固得弹出,可以看出GC没有回收t的意思。
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
MessageBox.Show("Collect function invok complete.");
}