В данной статье я рассмотрю проблемы утечек памяти в
программах на C#,
а также способы их решения через WeakReference.
Так же будут рассмотрены Weak Event Patterns для
устранения утечек памяти в Windows
Presentation
Foundation
(WPF).
При использовании событий с использованием ключевого
слова event, подписка на событие
создает ссылку из объекта, содержащего событие, на объект-подписчик.
Если исходный объект живет дольше, чем
объект-подписчик, возможны утечки памяти: при отсутствии других ссылок на
подписчика, на него по-прежнему будет ссылаться исходный объект. Поэтому
память, которую занимает объект-подписчик, не может быть освобождена сборщиком
мусора.
Детально ознакомиться со способами устранения утечек памяти Вы можете в статье Weak Events in C#. Практически 90% решений, связанных с утечками памяти при использовании
событий, решаются через WeakReference. Поэтому моя цель показать, как можно этого достичь.
public sealed class WeakEventHandler<TEventArgs> where TEventArgs : EventArgs
{
private readonly WeakReference _targetReference;
private readonly MethodInfo _method;
public WeakEventHandler(EventHandler<TEventArgs> callback)
{
_method = callback.Method;
_targetReference = new WeakReference(callback.Target, true);
}
public void Handler(object sender, TEventArgs e)
{
var target = _targetReference.Target;
if (target != null)
{
var callback = (Action<object, TEventArgs>)Delegate.CreateDelegate(typeof(Action<object, TEventArgs>), target,
_method, true);
if (callback != null)
{
callback(sender, e);
}
}
}
}
Unit test
[TestFixture]
public class WeakEventsTests
{
#region
Example
public class Alarm
{
public event PropertyChangedEventHandler Beeped;
public void Beep()
{
var handler = Beeped;
if (handler != null) handler(this, new PropertyChangedEventArgs("Beep!"));
}
}
public class Sleepy
{
private readonly Alarm _alarm;
private int _snoozeCount;
public Sleepy(Alarm alarm)
{
_alarm = alarm;
_alarm.Beeped += new WeakEventHandler<PropertyChangedEventArgs>(Alarm_Beeped).Handler;
}
private void Alarm_Beeped(object sender, PropertyChangedEventArgs e)
{
_snoozeCount++;
}
public int SnoozeCount
{
get { return _snoozeCount; }
}
}
#endregion
[Test]
public void
ShouldHandleEventWhenBothReferencesAreAlive()
{
var alarm = new Alarm();
var sleepy = new Sleepy(alarm);
alarm.Beep();
alarm.Beep();
Assert.AreEqual(2, sleepy.SnoozeCount);
}
[Test]
public void
ShouldAllowSubscriberReferenceToBeCollected()
{
var alarm = new Alarm();
var sleepyReference = null as WeakReference;
new Action(() =>
{
// Run this in a delegate to that
the local variable gets garbage collected
var sleepy = new Sleepy(alarm);
alarm.Beep();
alarm.Beep();
Assert.AreEqual(2, sleepy.SnoozeCount);
sleepyReference = new WeakReference(sleepy);
})();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Assert.IsNull(sleepyReference.Target);
}
[Test]
public void
SubscriberShouldNotBeUnsubscribedUntilCollection()
{
var alarm = new Alarm();
var sleepy = new Sleepy(alarm);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
alarm.Beep();
alarm.Beep();
Assert.AreEqual(2, sleepy.SnoozeCount);
}
}
Как
видим из примера, использование WeakReference
не
составляет труда. Объект сам будет уничтожен, как только на него перестанут
ссылаться.
Шаблоны слабых событий в WPF (Weak Event Patterns)
В
WPF
для
решения проблемы с утечкой памяти при использовании событий представляется шаблон, который можно
использовать путем предоставления выделенного класса
диспетчера для конкретных событий и реализации интерфейса прослушивателей для
данного события. Этот шаблон разработки называется шаблоном слабых событий.
Существует три способа реализации шаблона слабых событий.
Способ №1. Использовать
существующий простой класс диспетчера событий
Если событие, на
которое следует подписаться, имеет сопоставления WeakEventManager,
используйте существующий простой класс диспетчера событий. Список диспетчеров
слабых событий, входящих в состав WPF, приведён в разделе иерархии наследования
класса WeakEventManager.
Обратите внимание, что относительно небольшое количество слабых событий,
входящих в состав WPF диспетчеров, скорее всего, будет необходимо выбрать один
из прочих подходов.
Способ №2. Использование
класса диспетчера как универсальный шаблон слабых событий
Использование шаблона WeakEventManager<TEventSource,
TEventArgs> в случае, если WeakEventManager недоступен
и приоритетным является простой способ реализации перед эффективностью использования.
Шаблон WeakEventManager<TEventSource, TEventArgs> менее эффективный, по сравнению с WeakEventManager, а также пользовательским диспетчером слабых событий. Например, универсальный шаблон класса выполняет несколько отражений для обнаружения события по заданному имени события.
Шаблон WeakEventManager<TEventSource, TEventArgs> менее эффективный, по сравнению с WeakEventManager, а также пользовательским диспетчером слабых событий. Например, универсальный шаблон класса выполняет несколько отражений для обнаружения события по заданному имени события.
Способ №3. Создание
пользовательского класса диспетчера слабых событий
Этот способ подразумевает создание
пользовательского менеджера, наследуемого от WeakEventManager,
с целью обеспечить наилучшую производительность. Использование пользовательского
интерфейса WeakEventManager
с тем, чтобы подписаться на событие, является более эффективным, чем использование
универсального шаблона слабых событий.
Пример использования:
#region Constructor
public MainWindow()
{
InitializeComponent();
WeakEventManager<Window, EventArgs>
.AddHandler(App.Current.MainWindow, "Activated", MainWindow_Activated);
}
#endregion
void MainWindow_Activated(object sender, EventArgs e)
{
//Do
something here
}
Event Aggregators
Агрегаторы
событий (Event Aggregators) предлагают альтернативу
традиционным C #
событиям, с дополнительным преимуществом полной развязки издателя и подписчиков
событий. Каждый, кто может получить доступ к агрегатору события, может
опубликовать событие на него, и он не может быть подписан ни от кого другого,
имеющего доступ к агрегатору события.
Как работают Event Aggregators, можно посмотреть здесь.
Как работают Event Aggregators, можно посмотреть здесь.
Источники:
Event Aggregator with Reactive Extensions
No comments:
Post a Comment