События .NET, перезванивающие после того, как вызываемый абонент выходит из области видимости: безопасная практика? - PullRequest
3 голосов
/ 02 марта 2012

(извиняюсь, если это произошло раньше, я искал, но ничего не нашел для своих условий поиска)

Учитывая следующее:

void Method1 {
    Foo _foo = new Foo();
    _foo.DataReady += ProcessData();
    _foo.StartProcessingData();
}

void ProcessData() { //do something }

StartProcessingData() - это длинныйметод выполнения, который в конечном итоге ( и асинхронно ) вызывает событие DataReady.(скажем, он выполняет вызов службы или что-то в этом роде)

Теперь _foo раньше была переменной уровня класса, а событие раньше связывалось в конструкторе.

Однако, профилирование памяти показало, как это навсегда сохранит _foo и все его зависимые элементы в памяти, поэтому я и изменил вышесказанное.

Мой вопрос: есть ли когда-нибудь случай, когда GC разрушит вещи?Method1 заканчивается быстро (и, конечно, до того, как событие произойдет), что означает, что _foo перестает существовать.Однако означает ли это, что (поскольку _foo хранит ссылки на свои события) ProcessData() никогда не сработает?Или, достаточно ли наличия события, чтобы _foo оставалось живым после окончания метода, достаточно, чтобы обеспечить запуск ProcessData?Или это неокончательно?

[В тестировании все работало нормально - всегда вызывается ProcessData.Даже создание StartProcessingData заняло много времени, а принудительное выполнение сбора GC (с помощью RedGate Memory Profiler) не удаляло его.Но я хотел бы быть уверен!]

Для уточнения: StartProcessingData() возвращается немедленно.Объект Foo будет выглядеть примерно так:

class Foo
{
SomeSerice _service;
event EventHandler<EventArgs> DataReady;

Foo()
{
_service = new SomeService();
_service.ServiceCallCompleted += _service_ServiceCallCompleted;
}

void StartProcessingData()
{
_service.ServiceCallAsync();
}

void _service_ServiceCallCompleted
{
DataReady(null,e);
}

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


Вот полный рабочий пример (консольное приложение)

class Program
        {
            static void Main(string[] args)
            {
                Class1 _class1 = new Class1();
                Console.WriteLine("Disposing of Class 1");
                _class1 = null;

                GC.Collect();

                System.Threading.Thread.Sleep(15000);
                Console.Read();

            }

        }

        internal class Class1
        {
            internal Class1()
            {
                Foo _foo = new Foo();
                _foo.DataReady += new EventHandler<EventArgs>(_foo_DataReady);

                _foo.StartProcessingData();
            }

            void _foo_DataReady(object sender, EventArgs e)
            {
                Console.WriteLine("Class 1 Processing Data");
            }
        }

        class Foo
        {
            internal event EventHandler<EventArgs> DataReady = delegate { };

            internal void StartProcessingData()
            {
                System.Threading.Timer _timer = new System.Threading.Timer(OnTimer);

                Console.WriteLine("Firing event in 10 secs");
                _timer.Change(10000, System.Threading.Timeout.Infinite);
            }

            private void OnTimer(object state)
            {
                DataReady(this, null);
            }
    }

Если вы запустите его, вы получите:

Firing event in 10 secs
Disposing of Class 1
Class 1 Processing Data

Ответы [ 2 ]

2 голосов
/ 02 марта 2012

Предположим, что StartProcessingData() является полностью синхронным (т. Е. Многопоточность не задействована).Он не вернется, пока не произойдет событие, и ProcessData() будет вызываться из в течение _foo.StartProcessingData().Если вы хотите убедиться в этом, поместите точку останова в ProcessData() и посмотрите на стек вызовов.

Так что, как говорится, _foo не выйдет из области действия, когда событие запущено ивызывается обработчик, потому что Method1() не вернулся.

Теперь, если в потоке задействован , это означает, что код, выполняющийся в другом потоке, должен содержать ссылку на _foo;в противном случае событие было бы невозможно запустить.Поэтому _foo все еще не является кандидатом на сборку мусора.Таким образом, в любом случае вам не нужно беспокоиться о _foo получении мусора.

(edit)

Перехват события ServiceCallCompleted_service теперь означает, что _service содержит ссылку на _foo, предотвращая сбор мусора.

0 голосов
/ 02 марта 2012

Для объекта Foo возможно подписаться на события из объекта Bar, а затем отказаться от всех ссылок на Bar, в большинстве случаев не влияя на способность Bar запускать события, поскольку Barпредположительно, его события сработали бы в результате какого-либо потока или внешнего объекта, что могло бы произойти, только если этот поток или внешний объект имел ссылку на него.Есть две причины, по которым это может быть важно Foo сохраняет ссылку на Bar;в вашем случае это не применимо.

  1. Если `Foo` не хранит ссылки на` Bar`, возможно, он не сможет сообщить `Bar`, если позже он окажется без необходимостиУслуги бара.Он может попытаться использовать параметр `Sender` некоторого более позднего обратного вызова события, чтобы отписаться, но есть как минимум две проблемы с этим: (1) Иногда объект вызывает события от имени другого, в этом случае свойство` Sender` можетне идентифицировать объект, содержащий подписку на событие;(2) Возможно, что событие никогда не будет инициировано, но ссылка на подписчика может помешать подписчику иметь право на сборку мусора до тех пор, пока не будет опубликован.
  2. Вполне возможно (хотя и маловероятно), что единственными другими ссылками на `Bar` в любом месте системы могут быть типы` WeakReference`, и, таким образом, если `Foo` не сохраняет сильную ссылку на` Bar`,система может лишить законной силы все эти слабые ссылки, в результате чего `Bar` исчезнет.

Лично я считаю, что для объектов хорошая идея отменить все подписки, прежде чем они будут отменены.Можно ожидать, что издатель событий выйдет из области действия примерно во время отказа от подписчика, но если события останутся подключенными, то все, что поддерживает издателя, будет бесполезно поддерживать живых подписчиков.Если кто-либо из этих оставленных подписчиков также является издателем событий, их оставленные подписчики, в свою очередь, могут остаться в живых бесполезно и т. Д.

...