1

首先,为这个问题的长度道歉,但我想从一开始就充分解释自己。

好的,有点背景。我一直在研究一些使用WeakReference对象实现弱事件模式的代码。在这样做的过程中,我遇到了在发布者停止引发事件的某些情况下对象被泄露的常见问题。如果您有兴趣,那么那里有很多关于模式的信息。WPF 团队实施了WeakEventManager来解决我认为使用调度程序检查任何泄漏的对象并摆脱它们的问题。

我决定尝试不同的方法。我不想使用调度程序,而是想使用垃圾收集来触发泄漏对象的检测和取消订阅。这对我来说似乎是合乎逻辑的,因为任何WeakReference对象的目标都只是作为集合的结果而被删除。这导致我编写了一些代码,当垃圾收集发生时会引发一个事件。首先,我研究了使用框架的GC.RegisterForFullGCNotification机制,但很快意识到这是不可行的,因为它不能与并发垃圾收集一起使用。然后我对该主题进行了相当多的阅读并找到了 Jeffrey Richter 的解决方案,但这有几个问题,只会提醒您 gen0 和 gen2 集合

长话短说,我构建了以下简单的类。此类的目的是生成已发生垃圾回收的事件通知。但是,生成这些通知的机制是基于检测器对象的最终确定。因此,在进行垃圾回收时不会引发事件,但之后会在运行终结器线程时引发。

using System;

namespace Test
{
    /// <summary>
    /// Class responsible for monitoring garbage collections.
    /// </summary>
    public static class GarbageCollectionMonitor
    {
        private static readonly object syncLock;
        private static int generation0CollectionCount;
        private static EventHandler<EventArgs> generation0Subscriptions;
        private static int generation1CollectionCount;
        private static EventHandler<EventArgs> generation1Subscriptions;
        private static int generation2CollectionCount;
        private static EventHandler<EventArgs> generation2Subscriptions;

        public static event EventHandler<EventArgs> Generation0GarbageCollected
        {
            add
            {
                lock (GarbageCollectionMonitor.syncLock)
                {
                    GarbageCollectionMonitor.generation0Subscriptions = (EventHandler<EventArgs>)Delegate.Combine(  GarbageCollectionMonitor.generation0Subscriptions,
                                                                                                                    value);
                }
            }

            remove
            {
                lock (GarbageCollectionMonitor.syncLock)
                {
                    GarbageCollectionMonitor.generation0Subscriptions = (EventHandler<EventArgs>)Delegate.Remove(   GarbageCollectionMonitor.generation0Subscriptions,
                                                                                                                    value);
                }
            }
        }

        public static event EventHandler<EventArgs> Generation1GarbageCollected
        {
            add
            {
                lock (GarbageCollectionMonitor.syncLock)
                {
                    GarbageCollectionMonitor.generation1Subscriptions = (EventHandler<EventArgs>)Delegate.Combine(  GarbageCollectionMonitor.generation1Subscriptions,
                                                                                                                    value);
                }
            }

            remove
            {
                lock (GarbageCollectionMonitor.syncLock)
                {
                    GarbageCollectionMonitor.generation1Subscriptions = (EventHandler<EventArgs>)Delegate.Remove(   GarbageCollectionMonitor.generation1Subscriptions,
                                                                                                                    value);
                }
            }
        }

        public static event EventHandler<EventArgs> Generation2GarbageCollected
        {
            add
            {
                lock (GarbageCollectionMonitor.syncLock)
                {
                    GarbageCollectionMonitor.generation2Subscriptions = (EventHandler<EventArgs>)Delegate.Combine(  GarbageCollectionMonitor.generation2Subscriptions,
                                                                                                                    value);
                }
            }

            remove
            {
                lock (GarbageCollectionMonitor.syncLock)
                {
                    GarbageCollectionMonitor.generation2Subscriptions = (EventHandler<EventArgs>)Delegate.Remove(   GarbageCollectionMonitor.generation2Subscriptions,
                                                                                                                    value);
                }
            }
        }

        /// <summary>
        /// Constructs the garbage collection monitor type
        /// </summary>
        static GarbageCollectionMonitor()
        {
            GarbageCollectionMonitor.syncLock = new object();

            // Construct a detector object
            //
            // N.B. No reference to the detector is held so that it can immediately be
            // collected by the garbage collector.
            new Detector();
        }

        /// <summary>
        /// Class responsible for detecting the operation of the garbage collector
        /// via its finalization method
        /// </summary>
        private sealed class Detector
        {
            /// <summary>
            /// Constructs a detector object
            /// </summary>
            public Detector()
            {
            }

            /// <summary>
            /// Finalizes a detector object
            /// </summary>
            ~Detector()
            {
                // Get the generation 0 collection count
                //
                // Since the finalizer thread is frozen when the garbage collector is
                // operating there is no danger of race conditions when retrieving the
                // garbage collection counts
                int generation0CollectionCount = GC.CollectionCount(0);

                // Determine if the current generation 0 collection count is greater than
                // the monitor's generation 0 collection count
                //
                // This indicates that a generation 0 garbage collection has taken place
                // since the last time a detector object was finalized.
                if (generation0CollectionCount > GarbageCollectionMonitor.generation0CollectionCount)
                {
                    // Update the monitor's generation 0 collection count to the current
                    // collection count
                    GarbageCollectionMonitor.generation0CollectionCount = generation0CollectionCount;

                    // Process any generation 0 event subscriptions
                    this.ProcessSubscriptions(GarbageCollectionMonitor.generation0Subscriptions);
                }

                int generation1CollectionCount = GC.CollectionCount(1);

                if (generation1CollectionCount > GarbageCollectionMonitor.generation1CollectionCount)
                {
                    GarbageCollectionMonitor.generation1CollectionCount = generation1CollectionCount;

                    this.ProcessSubscriptions(GarbageCollectionMonitor.generation1Subscriptions);
                }

                int generation2CollectionCount = GC.CollectionCount(2);

                if (generation2CollectionCount > GarbageCollectionMonitor.generation2CollectionCount)
                {
                    GarbageCollectionMonitor.generation2CollectionCount = generation2CollectionCount;

                    this.ProcessSubscriptions(GarbageCollectionMonitor.generation2Subscriptions);
                }

                // Construct a new generation 0 detector object
                new Detector();
            }

            /// <summary>
            /// Processes event subscriptions
            /// </summary>
            /// <param name="subscriptions">The subscriptions</param>
            private void ProcessSubscriptions(EventHandler<EventArgs> subscriptions)
            {
                // N.B. A local reference to the subscriptions delegate is required because
                // this method is run on the finalizer thread which is started AFTER the
                // garbage collector has finished running. As a result it is likely that
                // the application threads that were frozen by the garbage collector will
                // have been thawed. Since delegates are immutable, by getting a local
                // reference the processing of the subscriptions is made thread-safe as any
                // attempt by another thread to asynchronously add or remove a subscription
                // will result in a separate new delegate being constructed.

                // Determine if any event subscriptions need to be invoked
                //
                // N.B. If a local reference were not used then there would be a risk of
                // the following:
                //
                // (1) The null reference inequality check yields a true result.
                // (2) The finalizer thread is paused.
                // (3) Another thread removes all subscriptions to the event causing the
                // subscriptions delegate to be replaced with a null reference.
                // (4) The finalizer thread is unpaused.
                // (5) The attempt to invoke the subscriptions delegate results in a null
                // reference exception being thrown.
                if (subscriptions != null)
                {
                    // Invoke the event
                    subscriptions.Invoke(   this,
                                            EventArgs.Empty);
                }
            }
        }
    }
}

它似乎运行良好,但是使用以下代码对其进行测试时...

    private void Gen0GarbageCollected(  object sender,
                                        System.EventArgs e)
    {
        Console.Write("Gen0  " + GC.CollectionCount(0) + Environment.NewLine);
    }

    private void Gen1GarbageCollected(  object sender,
                                        System.EventArgs e)
    {
        Console.Write("Gen1  " + GC.CollectionCount(1) + Environment.NewLine);
    }

    private void Gen2GarbageCollected(  object sender,
                                        System.EventArgs e)
    {
        Console.Write("Gen2  " + GC.CollectionCount(2) + Environment.NewLine);
    }

...我得到以下结果

    Gen0    1
    Gen0    2
    Gen1    1
    Gen0    3
    Gen0    4
    Gen1    2
    Gen2    1
    Gen0    5
    Gen1    3
    Gen2    2
    Gen0    7
    Gen0    8
    Gen0    9
    Gen1    4
    Gen0    10
    Gen0    11
    Gen0    12
    Gen1    5
    Gen2    3
    Gen0    14
    Gen0    15
    Gen0    16
    Gen1    6
    Gen0    17

似乎并非所有垃圾回收都触发了终结线程。在此示例中,第 0 代的第 6 和第 13 个集合没有引发事件。

现在(终于)来了问题。

我对此感到困惑,需要了解它为什么会发生。我的理由是,由于无法保证终结器线程何时运行,因此在正在运行的终结器线程的实例之间可能会发生多个垃圾收集(任何一代)。

如果是这种情况,那么可以给出的唯一保证是,如果引发了一个事件,那么自上次引发该事件以来已经发生了该代的垃圾收集。

这是我能想到的最好的方法,但是如果对垃圾收集内部有更多了解的人可以确认它是否正确并且我的解决方案中没有巨大的实现错误,我将不胜感激。

谢谢你陪我。

4

2 回答 2

1

为什么不使用 a ConditionalWeakTable<TKey, TValue>,其中键是您要监视以进行清理的对象,而值是特制的CriticalHandleReleaseHandle通过在代码中设置静态布尔标志来实现,这意味着“是时候清理处理程序了”?

然后,您可以使用单独的计时器来启动检查布尔标志的线程/任务,如果是true,则运行您的手动清理(此清理不会也不应该在终结器或调用期间执行ReleaseHandle)。

于 2013-08-07T22:30:41.950 回答
0

只有两种情况真正重要的是死的弱事件订阅是否被清理:

  • 每次调用相关事件时,系统都会浪费时间处理任何尚未清理的死订阅。

  • 如果可以在不清理死订阅的情况下添加无限数量的新订阅者,则死订阅的数量可能会无限增长。

如果从不调用事件并且没有添加新订阅,则不会花费时间处理死事件,并且事件占用的内存总量不会增加。因此,让他们坐下通常不会有任何真正的伤害。

我建议您可能希望在事件调用期间观察到死事件时让代码清理它们,否则让每个订阅请求检查几个弱订阅并在它们死亡时清理它们,或者有如果自上次扫描以来添加的数量足以超过在该扫描期间发现的活动数量,则事件订阅请求会扫描所有事件订阅。可以让检查死订阅的代码监视已执行的 GC 周期总数,如果迄今为止已执行的 GC 周期数等于上次扫描开始之前已执行的数,则跳过检查,但它可能并不重要。

于 2013-08-07T22:05:15.390 回答