Во-первых, извиняюсь за объем этого вопроса, но я хочу полностью объяснить себя с самого начала.
Хорошо, немного предыстории. Я работал над кодом, который реализует шаблон слабого события с помощью объекта WeakReference
. При этом я столкнулся с обычной проблемой утечки объектов в определенных сценариях, когда издатель перестает генерировать события. Если вам интересно, то есть немного информации о узор там. Команда WPF реализовала WeakEventManager для решения проблемы, которая Я считаю, что использует планировщик для проверки каких-либо просочившихся объектов и избавляется от них.
Я решил попробовать другой подход. Вместо использования планировщика я хотел запустить обнаружение и отказ от подписки на просочившиеся объекты с помощью сборок мусора. Мне это кажется логичным, поскольку цели любых WeakReference
объектов удаляются только в результате коллекций. Затем это заставило меня написать код, который будет вызывать событие, когда происходит сборка мусора. Сначала я рассмотрел возможность использования GC.RegisterForFullGCNotification
механизма фреймворка, но быстро понял, что это невозможно, поскольку он нельзя использовать с одновременной сборкой мусора. Затем я немного почитал по этой теме и нашел решение, но у него есть пара проблем, и оно предупреждает вас только о коллекциях 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
Кажется, что не все сборки мусора запускают поток завершения. В этом примере 6-я и 13-я коллекции поколения 0 не вызывают событий.
Теперь (наконец) возникает вопрос.
Меня это сбивает с толку, и мне нужно понять, почему это происходит. Мое рассуждение состоит в том, что, поскольку нет гарантии, когда будет запущен поток финализатора, возможно, что между экземплярами запущенного потока финализатора может произойти несколько сборок мусора (любого поколения).
Если это так, то единственной гарантией, которую можно дать, является то, что в случае возникновения события сборка мусора этого поколения имела место с момента последнего возникновения события.
Это лучшее, что я могу придумать, но я был бы очень признателен, если бы кто-то с более глубокими знаниями о внутреннем устройстве сборки мусора мог подтвердить, что это правильно, и у меня нет огромной ошибки реализации в моем решении.
Спасибо за терпение.