代码之家  ›  专栏  ›  技术社区  ›  supercat

终结器访问托管内容

  •  4
  • supercat  · 技术社区  · 14 年前

    我很清楚终结器通常用于控制非托管资源。在什么情况下,定稿人可以处理管理的问题?

    我的理解是,在终结器队列中的存在将阻止收集任何对象或其中强烈引用的对象,但它(当然)不会保护它们不被终结。在正常的事件过程中,一旦一个对象完成,它将从队列中删除,并且它引用的任何对象将不再受到下次GC传递时收集的保护。当调用终结器时,终结器可能已被调用,用于对象引用的任何对象组合;不能依赖于以任何特定顺序调用的终结器,但一个保留的对象引用仍然有效。

    很明显,终结器决不能获取锁,也不能尝试创建新对象。但是,假设我有一个订阅某些事件的对象,以及另一个实际使用这些事件的对象。如果后一个对象符合垃圾收集的条件,我希望尽快取消前一个对象对事件的订阅。请注意,在任何活动对象都不持有前一个对象的订阅之前,前一个对象将永远不具备完成的资格。

    如果有一个需要取消订阅的对象的无锁链表堆栈或队列,并且让主对象的终结器引用堆栈/队列中的另一个对象,这是可行的吗?创建主对象时必须分配链接列表项对象(因为在终结器内的分配将被禁止),并且可能需要使用类似计时器事件的事件来轮询队列(因为事件取消订阅将必须在终结器线程外运行,并且它可能是愚蠢的T)o有一个线程,其唯一目的是等待某个事件出现在终结器队列中),但如果终结器可以安全地引用其预先分配的链接列表对象和与其类关联的主队列对象,则它可以允许在终结后15秒内取消订阅事件。

    那是个好主意吗?(注意:我使用的是.NET 2.0;另外,在threading.interlocked.compareexchange上,尝试添加到堆栈或队列可能会旋转几次,但我不希望它停留太久)。

    编辑

    当然,订阅事件的任何代码都应该实现IDisposable,但是一次性的东西并不总是正确地处理。如果有的话,就不需要定稿器了。

    我关注的场景是这样的:实现IEnumerator(of T)的类挂接其关联类的ChangeNotify事件,以便在基础类发生更改时可以明智地处理枚举(是的,我知道Microsoft认为所有枚举器都应该放弃,但有时可以保留工作会更有用)。类的一个实例很可能在数天或数周内被枚举数千次甚至数百万次,但在这段时间内根本没有被更新。

    理想情况下,枚举器在没有被释放的情况下永远不会被忘记,但有时在“foreach”和“using”不适用的情况下使用枚举器(例如,某些枚举器支持嵌套枚举)。一个精心设计的终结器可能允许一种方法来处理这个场景。

    顺便说一下,我要求任何应该通过更新继续的枚举都必须使用泛型IEnumerable(of T);不处理IDisposable的非泛型窗体在集合被修改时必须引发异常。

    4 回复  |  直到 14 年前
        1
  •  3
  •   Reed Copsey    14 年前

    但是,假设我有一个订阅某些事件的对象,以及另一个实际使用这些事件的对象。如果后一个对象符合垃圾收集的条件,我希望尽快取消前一个对象对事件的订阅。请注意,在任何活动对象都不持有前一个对象的订阅之前,前一个对象将永远不具备完成的资格。

    如果“后一个对象”是使用事件的对象,“前一个”对象是订阅事件的对象,“前一个”对象必须有某种方式将事件信息传递给“后一个”对象,这意味着它将有一些对“后一个”的引用。很可能,这将使“后一个”对象 曾经 作为GC候选人。


    也就是说,我建议避免通过终结器进行这种类型的托管资源分配,除非绝对必要。你描述的建筑看起来很脆弱,很难纠正。这可能是IDisposable的更好的候选者,因为终结器是“最后一道沟”清理工作。

    虽然 IDisposable 通常是关于释放本机资源-它可以是关于释放任何资源,包括订阅信息。

    另外,我会尽量避免有一个对象引用的全局集合-让您的对象在内部只使用 WeakReference . 一旦“后一个”对象被收集,“前一个”对象的weakreference将不再有效。下次引发事件订阅时,如果内部weakreference不再有效,您可以自己取消订阅。不需要全局队列、列表等-它应该只起作用…

        2
  •  0
  •   FMM    14 年前

    我将把对象称为“发布者”和“订户”,并重申我对问题的理解:

    在C中,发布服务器将(有效地)保存对订阅服务器的引用,防止订阅服务器被垃圾收集。我该怎么做才能在不显式管理订阅的情况下对订阅服务器对象进行垃圾收集?

    首先,我建议你做 我能做的一切 首先要避免这种情况。现在,我要继续假设你有,考虑到你无论如何都要发布这个问题=)

    接下来,我建议挂接发布者事件的添加和删除访问器,并使用weakreferences集合。然后,只要调用事件,就可以自动取消这些订阅的挂起。这里有一个 非常粗糙,未经测试 例子:

    private List<WeakReference> _eventRefs = new List<WeakReference>();
    
    public event EventHandler SomeEvent
    {
        add
        {
            _eventRefs.Add(new WeakReference(value));
        }
        remove
        {
            for (int i = 0; i < _eventRefs; i++)
            {
                var wRef = _eventRefs[i];
                if (!wRef.IsAlive)
                {
                    _eventRefs.RemoveAt(i);
                    i--;
                    continue;
                }
    
                var handler = wRef.Target as EventHandler;
                if (object.ReferenceEquals(handler, value))
                {
                    _eventRefs.RemoveAt(i);
                    i--;
                    continue;
                }
            }
        }
    }
    
        3
  •  0
  •   JMarsch    14 年前

    让我确保我理解——您担心来自仍然订阅收集到的事件发布者的事件订阅服务器的泄漏吗?

    如果是这样的话,我想你不必担心。

    我的意思是假设“前”对象是事件订阅服务器,“后”对象是事件发布服务器(引发事件):

    订阅服务器(前者)被“订阅”的唯一原因是您创建了一个委托对象并将该委托传递给发布服务器(“后者”)。

    如果查看委托成员,它将引用订阅服务器对象和将要执行的订阅服务器上的方法。所以有一个引用链看起来像这样:publisher-->delegate-->subscriber(publisher引用delegate,后者引用subscriber)。这是一个单向链——订阅方不包含对委托的引用。

    因此,保持代理的唯一根目录是发布服务器(“后者”)。当后者符合GC的条件时,代理也符合。除非您希望订户在取消订阅时采取某些特殊措施,否则当收集到代理时,他们将有效地变为取消订阅--没有泄漏)。

    编辑

    根据supercat的评论,问题似乎在于发布服务器使订阅服务器保持活动状态。

    如果这是问题所在,那么终结器将不会帮助您。原因:您的发布服务器对您的订阅服务器(通过代理)有一个真正的、实质性的引用,并且发布服务器是根目录(否则它将符合GC的条件),因此您的订阅服务器是根目录,并且不符合定稿或GC的条件。

    如果您在使订阅服务器保持活动状态方面遇到问题,我建议您搜索弱引用事件。以下是一些链接,可以帮助您开始: http://www.codeproject.com/KB/cs/WeakEvents.aspx http://www.codeproject.com/KB/architecture/observable_property_patte.aspx .

    我也得处理一次。大多数有效的模式都涉及到更改发布者,使其对委托具有弱引用。然后你就有了一个新的问题——委托并没有根深蒂固,你必须以某种方式保持它的活力。上面的文章可能是这样的。有些技术使用反射。

    我曾经用过一种不依赖思考的技巧。但是,它要求您能够在发布服务器和订阅服务器中对代码进行更改。如果你想看看那个溶液的样品,请告诉我。

        4
  •  0
  •   FMM    14 年前

    让我们再试一次。是否可以将事件处理程序添加到发布服务器,如下所示:

    var pub = new Publisher();
    var sub = new Subscriber();
    var ref = new WeakReference(sub);
    
    EventHandler handler = null; // gotta do this for self-referencing anonymous delegate
    
    handler = (o,e) =>
    {
        if(!ref.IsAlive)
        {
            pub.SomeEvent -= handler; // note the self-reference here, see comment above
            return;
        }
    
    
        ((Subscriber)ref.Target).DoHandleEvent();
    };
    
    pub.SomeEvent += handler;
    

    这样,您的委托就不会直接引用订阅者,并且在订阅者被收集时自动将其自身取消挂钩。您可以将它实现为订阅服务器类的私有静态成员(为了封装的目的),只要确保它是静态的,以防止无意中持有对“this”对象的直接引用。