事件处理程序在CLR的垃圾回收中的影响

3

我完全不明白事件处理程序如何影响垃圾回收操作。

例如,为什么对象a1没有被垃圾回收(a1的解构器没有调用):

即使取消订阅timeChange事件处理程序后,解构器也没有被垃圾回收器调用。

此致

public class B
{
    private void button1_Click(object sender, EventArgs e)
    {    
        A a1 = new A();
        a1.timeChange += A1_timeChange;

        a1.Start();

        a1 = null;

        GC.Collect();
    }

    private void A1_timeChange(object sender, EventArgs e)
    {
        MessageBox.Show(((DateTime)sender).ToString() );
    }
}

public class A
{
    ~A()
    {
        MessageBox.Show("A Collected");
    }

    public void Start()
    {
        if (timeChange != null)
        {
            Task.Factory.StartNew(() => {
                while (true)
                {
                    timeChange(DateTime.Now, null);
                    System.Threading.Thread.Sleep(3000);
                }
            });
        }
    }
    public event EventHandler timeChange;
}

2
该任务通过 this.timeChange 成员保持引用,如果 this 变为未定义将是灾难性的。while (true) 循环确保它永远保持引用。任意将事件设为静态,你现在会看到它被收集了。 - Hans Passant
1个回答

1
总之
引起问题的不是事件本身,而是在长时间运行的线程中使用闭包引用类A的实例成员。
事件本身是否有问题?
这段代码 a1.timeChange += A1_timeChange; 导致类A内实现事件 public event EventHandler timeChange 的委托引用类B内的 A1_timeChange 方法。
所以,引用是相反的
在你的情况下,如果你消除了对类B的所有引用,但没有取消订阅事件,那么指向B中处理程序方法的A中的事件处理程序可以使类B仍然可达,因此不能被GC回收。
真正发生的事情
您的类A可以从GC根(特别是正在运行的线程)访问,因此它仍然可达,因此不会被回收-阅读更多
你使用了这段代码,产生了一个永远运行的任务(当应用关闭/前台线程终止时会停止)。
Task.Factory.StartNew(() => {
                while (true)
                {
                    timeChange(DateTime.Now, null);
                    System.Threading.Thread.Sleep(3000);
                }

事实上,您正在使用一个lambda表达式来关闭timeChange事件,这会使编译器生成一个仅引用类A的类。
还有一件事,只要有一个析构函数(编译为终结器),您就会将对象的生命周期延长一个GC收集周期 - 在第一个GC上,您的对象将被标记为不可达,并被放入终结队列中。在下一个GC上,终结器将实际运行 - 阅读更多

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接