Используя WeakReference для решения вопроса с.NET незарегистрированные обработчики событий, вызывающие утечки памяти

Проблема: Зарегистрированные обработчики событий создают ссылку от события до экземпляра обработчика событий. Если тому экземпляру не удается не зарегистрироваться, обработчик событий (через Располагают, по-видимому), то память экземпляра не будет освобождена сборщиком "мусора".

Пример:

    class Foo
    {
        public event Action AnEvent;
        public void DoEvent()
        {
            if (AnEvent != null)
                AnEvent();
        }
    }        
    class Bar
    {
        public Bar(Foo l)
        {
            l.AnEvent += l_AnEvent;
        }

        void l_AnEvent()
        {

        }            
    }

Если я инстанцирую Нечто и передам это новому конструктору Bar, то отпущенный объекта Панели, оно не будет освобождено сборщиком "мусора" из-за регистрации AnEvent.

Я считаю это утечкой памяти, и кажется точно так же, как мои старые дни C++. Я могу, конечно, сделать Панель IDisposable, не зарегистрировать событие в Расположении () метод и удостовериться, что звонил, Располагают () на экземплярах его, но почему мне придется сделать это?

Я сначала подвергаю сомнению, почему события реализованы с сильными ссылками? Почему бы не использовать слабые ссылки? Событие используется для абстрактного уведомления объекта изменений в другом объекте. Мне кажется, что, если экземпляр обработчика событий больше не используется (т.е. нет никаких ссылок непримечательного события на объект), то любые события, в которых это регистрируется, должны автоматически быть не зарегистрированы. Что я пропускаю?

Я посмотрел на WeakEventManager. Ничего себе, что боль. Мало того, что очень трудно использовать, но и его документация является несоответствующей (см. http://msdn.microsoft.com/en-us/library/system.windows.weakeventmanager.aspx - замечающий раздел "Notes to Inheritors", который имеет 6 неопределенно описанных маркеров).

Я видел другие обсуждения в различных местах, но ничто, я чувствовал, что мог использовать. Я предлагаю простое решение на основе WeakReference, как описано здесь. Мой вопрос: разве это не отвечает требованиям со значительно меньшей сложностью?

Для использования решения вышеупомянутый код изменяется следующим образом:

    class Foo
    {
        public WeakReferenceEvent AnEvent = new WeakReferenceEvent();

        internal void DoEvent()
        {
            AnEvent.Invoke();
        }
    }

    class Bar
    {
        public Bar(Foo l)
        {
            l.AnEvent += l_AnEvent;
        }

        void l_AnEvent()
        {

        }
    }

Уведомление две вещи: 1. Класс Нечто изменяется двумя способами: событие заменяется экземпляром WeakReferenceEvent, показанного ниже; и вызов события изменяется. 2. Класс Панели НЕИЗМЕНЕН.

Никакая потребность разделить WeakEventManager на подклассы, реализуйте IWeakEventListener и т.д.

Хорошо, таким образом, на реализации WeakReferenceEvent. Это показывают здесь. Обратите внимание, что это использует универсальный WeakReference , который я одолжил отсюда: http://damieng.com/blog/2006/08/01/implementingweakreferencet

class WeakReferenceEvent
{
    public static WeakReferenceEvent operator +(WeakReferenceEvent wre, Action handler)
    {
        wre._delegates.Add(new WeakReference(handler));
        return wre;
    }

    List> _delegates = new List>();

    internal void Invoke()
    {
        List> toRemove = null;
        foreach (var del in _delegates)
        {
            if (del.IsAlive)
                del.Target();
            else
            {
                if (toRemove == null)
                    toRemove = new List>();
                toRemove.Add(del);
            }
        }
        if (toRemove != null)
            foreach (var del in toRemove)
                _delegates.Remove(del);
    }
}

Это - функциональность, тривиально. Я переопределяю оператор + для получения + = синтаксические события соответствия сахара. Это создает WeakReferences делегату Действия. Это позволяет сборщику "мусора" освобождать целевой объект события (Панель в этом примере), когда никто больше не держится за него.

В Вызывании () метод, просто пробегает слабые ссылки и называют их Целевое Действие. Если какие-либо мертвые (т.е. собрал "мусор") ссылки найдены, удаляют их из списка.

Конечно, это только работает с делегатами Действия типа. Я попытался делать этот дженерик, но столкнулся с пропавшими без вести где T: делегат в C#!

Как альтернатива, просто измените класс WeakReferenceEvent, чтобы быть WeakReferenceEvent и заменить Действие Действием . Зафиксируйте ошибки компилятора, и у Вас есть класс, который может использоваться как так:

    class Foo
    {
        public WeakReferenceEvent AnEvent = new WeakReferenceEvent();

        internal void DoEvent()
        {
            AnEvent.Invoke(5);
        }
    }

Полный код с и оператор - (для удаления событий) показывают здесь:

class WeakReferenceEvent
{
    public static WeakReferenceEvent operator +(WeakReferenceEvent wre, Action handler)
    {
        wre.Add(handler);
        return wre;
    }
    private void Add(Action handler)
    {
        foreach (var del in _delegates)
            if (del.Target == handler)
                return;
        _delegates.Add(new WeakReference>(handler));
    }

    public static WeakReferenceEvent operator -(WeakReferenceEvent wre, Action handler)
    {
        wre.Remove(handler);
        return wre;
    }
    private void Remove(Action handler)
    {
        foreach (var del in _delegates)
            if (del.Target == handler)
            {
                _delegates.Remove(del);
                return;
            }
    }

    List>> _delegates = new List>>();

    internal void Invoke(T arg)
    {
        List>> toRemove = null;
        foreach (var del in _delegates)
        {
            if (del.IsAlive)
                del.Target(arg);
            else
            {
                if (toRemove == null)
                    toRemove = new List>>();
                toRemove.Add(del);
            }
        }
        if (toRemove != null)
            foreach (var del in toRemove)
                _delegates.Remove(del);
    }
}

Надо надеяться, это поможет кому-то еще, когда они столкнутся с вызванной утечкой памяти события тайны в собравшем "мусор" мире!

11
задан Eric 11 May 2010 в 23:38
поделиться

2 ответа

Я нашел ответ на свой вопрос, почему это не работает. Да, действительно, я упустил незначительную деталь: Вызов += для регистрации события (l.AnEvent += l_AnEvent;) создает неявный объект Action. Этот объект обычно хранится только в самом событии (и в стеке вызывающей функции). Таким образом, когда вызов возвращается и запускается сборщик мусора, неявно созданный объект Action освобождается (теперь на него указывает только слабая ссылка), а событие снимается с регистрации.

Болезненное решение состоит в том, чтобы держать ссылку на объект Action следующим образом:

    class Bar
    {
        public Bar(Foo l)
        {
            _holdAnEvent = l_AnEvent;
            l.AnEvent += _holdAnEvent;
        }
        Action<int> _holdAnEvent;
        ...
    }

Это работает, но лишает решение простоты.

2
ответ дан 3 December 2019 в 11:51
поделиться

Конечно, это повлияет на производительность.

Это немного похоже на то, зачем ссылаться на другие сборки в моем решении, если я могу использовать отражение для динамического чтения сборки и выполнения соответствующих вызовов в ее типах?

Короче ... вы используете сильная ссылка по двум причинам ... 1. Безопасность типов (здесь не применимо) 2. Производительность

Это восходит к аналогичным дебатам о дженериках по сравнению с хэш-таблицами. {{1 }} В прошлый раз я видел этот аргумент, помещенный в таблицу, однако плакат выглядел как предварительно созданный файл msil, может быть, это поможет вам пролить свет на проблему?

Еще одна мысль ... {{1} } Что, если бы вы прикрепили обработчик событий, чтобы сообщить о событии com-объекта? Технически этот объект не управляется, так как он узнает, когда он нуждается в очистке, ведь все сводится к тому, как фреймворк правильно обрабатывает область видимости?

Этот пост поставляется с «гарантией работы в моей голове», мы не несем ответственности за то, как этот пост изображен:)

2
ответ дан 3 December 2019 в 11:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: