首先,为这个问题的长度道歉,但我想从一开始就充分解释自己。
好的,有点背景。我一直在研究一些使用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 个集合没有引发事件。
现在(终于)来了问题。
我对此感到困惑,需要了解它为什么会发生。我的理由是,由于无法保证终结器线程何时运行,因此在正在运行的终结器线程的实例之间可能会发生多个垃圾收集(任何一代)。
如果是这种情况,那么可以给出的唯一保证是,如果引发了一个事件,那么自上次引发该事件以来已经发生了该代的垃圾收集。
这是我能想到的最好的方法,但是如果对垃圾收集内部有更多了解的人可以确认它是否正确并且我的解决方案中没有巨大的实现错误,我将不胜感激。
谢谢你陪我。