分享

.net中的游魂现象(转自其他博客)

 冷泉阁 2016-11-24

跳出三界外,不在五行中的timer:
在 使用timer的时候,发现timer类型对象的作用于比较奇特。一般而言,在函数内定义的变量,其作用域不超过函数,在函数结束的时候变量的生命周期就 结束了,但是万事里总会有个一,普遍规律下总有那么一些例外的东西,比如说timer,其作用域就不会因函数的结束而结束。考察以下事件处理函数,

       private void button1_Click(object sender, EventArgs e)
        {
            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的一些内核对象,在超离作用域的时候,没有对内核对象进行相应的处理,当然这只是推测,我现在没有精力去证实。其二:委托原因。其三 :使用了多线程技术
考察以下代码,

private void button2_Click(object sender, EventArgs e)
        {
            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的意义。


  private void button1_Click(object sender, EventArgs e)
        {
            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块后被回收了。


private void button4_Click(object sender, EventArgs e)
        {
            
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引用的资源。

 

        private void button4_Click(object sender, EventArgs e)
        {
            
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的意思。

        private void button3_Click(object sender, EventArgs e)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            MessageBox.Show(
"Collect function invok complete.");
        }

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多