4

我一直在阅读有关内存管理的内容,并且在一个项目中遇到过这样的情况,无论是这本书还是谷歌都没有给出确切的答案。我已经知道委托是管理对象,事件是委托实例。话虽如此,一旦应用程序结束,委托实例将从内存中删除。

我无法弄清楚的是如何确保在我的类被释放时(显式地或通过 GC)外部代码已释放所有事件引用。例如,类A公开一个事件,类使用B它。类B调用 Dispose 类A而不释放对委托的引用。当然,我们不能从 Dispose 方法本身抛出错误。

以下是一个具有委托的类和另一个使用它的类。

public class ClassB
{
    private ClassA A { get; set; }

    public ClassB()
    {
        this.A = new ClassA();
        this.A.OnProcessed += new ClassA.DelegateProcessed(this.ClassA_Processed);
    }

    public void Process()
    {
        this.A.Process();
    }

    public void ClassA_Processed (ClassA sender, EventArgs e)
    {
        // Do something.

        // Code written by another developer does not free up events before calling Dispose.

        this.A.Dispose();
        this.A = null;
    }
}

public class ClassA: IDisposable
{
    public delegate void DelegateProcessed (A sender, EventArgs e);
    public event DelegateProcessed OnProcessed = null;

    ~ClassA() { this.Dispose(false); }

    public void Dispose ()
    {
        this.Dispose(true);
        System.GC.SuppressFinalize(this);
    }

    private void Dispose (bool disposing)
    {
        if (!this.Disposed)
        {
            if (disposing)
            {
                // Dispose managed resources here.
                // Is it possible / advisable to dispose of delegates / events here?
                // Will this adversely affect the consumer class?
                this.OnProcessed -= new ClassA.DelegateProcessed(this.ClassA_Processed);
            }
        }
        this.Disposed = true;
    }

    public void Process () { this.OnProcessed(this, new EventArgs()); }

    public void ClassA_Processed (ClassA sender, EventArgs e) { }
}

关键是要确保无论开发人员对 ClassB 做什么,ClassA 都有资格进行垃圾收集。关键是即使消费者粗心,也要尽量减少 ClassA 在内存中花费的时间。

更新:从答案中可以清楚地看出,事件不必从 ClassA 中明确删除。至于主要问题,弱引用似乎是下面回答的方法。目标是最小化 ClassA 在内存中的停留时间。如果我忽略了任何事情,请告诉我。

4

4 回答 4

2

IDisposable用于确定性地释放非托管资源。

无需删除事件处理程序。例如,如果您查看 Windows 窗体FormUserControl类,或 ASP.NETPageUserControl类,所有这些都是IDisposable.,您会看到事件的广泛使用,并且在处理过程中没有特殊处理。

于 2012-09-24T14:04:06.933 回答
1

这部分代码:

private ClassA A { get; set; }

public ClassB()
{
    this.A = new ClassA();
    this.A.OnProcessed += new ClassA.DelegateProcessed(this.ClassA_Processed);
}

意味着你什么都不做。

一个B实例拥有一个A实例,并且再次A拥有一个引用(通过事件)B

当 aB变得无法访问时,A也会收集(GC 和循环引用)。

当'A'在'A'之前被处理(长)时,B'A'也将被收集(方向性)。

上的IDispoable界面A毫无意义。


关于实施:

 // class B
   this.A.OnProcessed += new ClassA.DelegateProcessed(this.ClassA_Processed);

 // in classA
   this.OnProcessed -= new ClassA.DelegateProcessed(this.ClassA_Processed);

这行不通,2 种不同this意味着它们是 2 种不同的方法。

于 2012-09-24T14:04:39.683 回答
1

您应该看看弱事件模式,而不是“经典”事件订阅。

事件订阅可以使对象保持活动状态,即使这些引用是唯一剩下的引用并且被引用的对象本身已经超出范围。在这种情况下,被引用的对象将永远不会被 GarbageCollector 收集并保持活动状态,直到您的应用程序结束。

这会导致严重的内存泄漏。

如果您使用弱事件模式,您可以让 GabageCollector 更好地确定对象是否仍被引用,或者事件是否是唯一的引用。在这种情况下,对象会被收集,您的资源会被释放。

于 2012-09-24T14:06:04.093 回答
0

正确编写的类应在其IDisposable.Dispose方法中取消订阅它已订阅的任何事件。如果订阅了事件的对象的 GC 生命周期与订阅的对象的有用生命周期相当(这是一种非常常见的情况),那么订阅是否被清理或悬空都无关紧要。不幸的是,如果A在没有取消订阅 ' 事件的情况下被放弃,并且某些东西保持对(有意或无意)B的长期引用,那么任何保持活动的东西也将保持活动状态,并且任何拥有直接或间接引用的东西(包括对象有来自的活动事件订阅BBAAA)。很容易得到相互连接的对象的大森林,这些对象通常有资格进行垃圾收集,但只要需要它们中的任何一个,它们必须保持活动状态。

太糟糕了事件订阅和取消订阅太尴尬了。如果有一个与事件关联的对象类型,一个要订阅各种事件的对象可以使用“事件管理器”对象来管理订阅(因此可以说类似MyEventManager.Subscribe(SomeObject.SomeEvent, someProc)然后MyEventManager.Dispose取消订阅它拥有的所有事件已建立订阅。不幸的是,没有合适的方法让方法接受事件作为参数,因此无法拥有通用类来管理传入订阅。最好的办法可能是拥有一个可以CleanupManager接受的类一对委托并被调用,例如 `MyCleaner.Register(()=>{SomeObject.SomeEvent += someProc;}, ()=>{SomeObject.SomeEvent -= someProc();}) 但这似乎很尴尬。

于 2012-09-24T16:29:46.120 回答