Нужно ли явно удалять обработчики событий в C #

c# garbage-collection event-handling

38808 просмотра

2 ответа

13023 Репутация автора

У меня есть класс, который предлагает несколько мероприятий. Этот класс объявляется глобально, но не создается в соответствии с этим глобальным объявлением - он создается по мере необходимости в нужных ему методах.

Каждый раз, когда этот класс нужен в методе, он создается и регистрируются обработчики событий. Нужно ли явно удалять обработчики событий, прежде чем метод выйдет из области видимости?

Когда метод выходит из области видимости, то выходит и экземпляр класса. Имеет ли обработчик событий, зарегистрированный для этого экземпляра, который выходит за пределы области, следствие памяти? (Мне интересно, если обработчик событий не позволяет GC видеть экземпляр класса, как на который больше не ссылаются.)

Автор: rp. Источник Размещён: 03.02.2009 05:28

Ответы (2)


7 плюса

47000 Репутация автора

В твоем случае у тебя все хорошо. Первоначально я прочитал ваш вопрос задом наперед, что подписчик выходил за рамки, а не издатель . Если издатель событий выходит из области видимости, то ссылки на подписчика (конечно, не самого подписчика!) Идут вместе с ним, и нет необходимости явно удалять их.

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

Если класс все еще зарегистрирован через обработчики событий, он все еще доступен. Это все еще живой объект. GC, следующий за графиком событий, найдет его подключенным. Да, вы захотите явно удалить обработчики событий.

Тот факт, что объект находится вне области его первоначального размещения, не означает, что он является кандидатом в GC. Пока живая ссылка остается, она живая.

Автор: Eddie Размещён: 03.02.2009 05:37

178 плюса

1135231 Репутация автора

Решение

В твоем случае все нормально. Это объект, который публикует события, которые поддерживают цели обработчиков событий. Так что если у меня есть:

publisher.SomeEvent += target.DoSomething;

тогда publisherесть ссылка, targetно не наоборот.

В вашем случае издатель будет иметь право на сборку мусора (при условии, что на него нет других ссылок), поэтому тот факт, что он получил ссылку на цели обработчика событий, не имеет значения.

Сложный случай, когда издатель является долгоживущим, но подписчики этого не хотят - в этом случае вам необходимо отписаться от обработчиков. Например, предположим, что у вас есть какая-то служба передачи данных, которая позволяет подписываться на асинхронные уведомления об изменениях пропускной способности, а объект службы передачи является долгоживущим. Если мы сделаем это:

BandwidthUI ui = new BandwidthUI();
transferService.BandwidthChanged += ui.HandleBandwidthChange;
// Suppose this blocks until the transfer is complete
transferService.Transfer(source, destination);
// We now have to unsusbcribe from the event
transferService.BandwidthChanged -= ui.HandleBandwidthChange;

(Вы на самом деле хотели бы использовать блок finally, чтобы убедиться, что вы не пропускаете обработчик событий.) Если бы мы не отписались, тогда они BandwidthUIбудут жить по крайней мере столько же, сколько и служба передачи.

Лично я редко сталкиваюсь с этим - обычно, если я подписываюсь на событие, цель этого события живет, по крайней мере, столько же, сколько и издатель - форма будет действовать столько же, сколько, например, кнопка, которая находится на нем. Стоит знать об этой потенциальной проблеме, но я думаю, что некоторые люди беспокоятся об этом, когда им это не нужно, потому что они не знают, в какую сторону идут ссылки.

РЕДАКТИРОВАТЬ: Это ответить на комментарий Джонатана Дикинсона. Во-первых, посмотрите на документы для Delegate.Equals (объект), которые явно дают поведение равенства.

Во-вторых, вот короткая, но полная программа, показывающая работоспособность отписки:

using System;

public class Publisher
{
    public event EventHandler Foo;

    public void RaiseFoo()
    {
        Console.WriteLine("Raising Foo");
        EventHandler handler = Foo;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
        else
        {
            Console.WriteLine("No handlers");
        }
    }
}

public class Subscriber
{
    public void FooHandler(object sender, EventArgs e)
    {
        Console.WriteLine("Subscriber.FooHandler()");
    }
}

public class Test
{
    static void Main()
    {
         Publisher publisher = new Publisher();
         Subscriber subscriber = new Subscriber();
         publisher.Foo += subscriber.FooHandler;
         publisher.RaiseFoo();
         publisher.Foo -= subscriber.FooHandler;
         publisher.RaiseFoo();
    }
}

Результаты:

Raising Foo
Subscriber.FooHandler()
Raising Foo
No handlers

(Проверено на Mono и .NET 3.5SP1.)

Дальнейшее редактирование:

Это нужно для того, чтобы доказать, что издатель событий может быть собран, пока еще есть ссылки на подписчика.

using System;

public class Publisher
{
    ~Publisher()
    {
        Console.WriteLine("~Publisher");
        Console.WriteLine("Foo==null ? {0}", Foo == null);
    }

    public event EventHandler Foo;
}

public class Subscriber
{
    ~Subscriber()
    {
        Console.WriteLine("~Subscriber");
    }

    public void FooHandler(object sender, EventArgs e) {}
}

public class Test
{
    static void Main()
    {
         Publisher publisher = new Publisher();
         Subscriber subscriber = new Subscriber();
         publisher.Foo += subscriber.FooHandler;

         Console.WriteLine("No more refs to publisher, "
             + "but subscriber is alive");
         GC.Collect();
         GC.WaitForPendingFinalizers();         

         Console.WriteLine("End of Main method. Subscriber is about to "
             + "become eligible for collection");
         GC.KeepAlive(subscriber);
    }
}

Результаты (в .NET 3.5SP1; здесь Mono ведет себя немного странно. Рассмотрим это некоторое время):

No more refs to publisher, but subscriber is alive
~Publisher
Foo==null ? False
End of Main method. Subscriber is about to become eligible for collection
~Subscriber
Автор: Jon Skeet Размещён: 03.02.2009 06:28
Вопросы из категории :
32x32