Что произойдет, если обработчик событий C # удалится, и я вызову его? - PullRequest
7 голосов
/ 01 декабря 2011

Этот вопрос является продолжением вопроса C # и безопасности потока (я не являюсь его автором) и соответствующего сообщения в блоге Эрика Липперта События и гонки, Есть и другие подобные вопросы по SO, но ни один из них на самом деле не рассматривает этот случай, общий консенсус заключается в том, что до тех пор, пока вы отписываетесь, вы в безопасности, но я не верю, что это всегда так.

Согласно обсуждению в вопросе SO и в блоге, шаблон, который следует использовать, выглядит примерно так:

var ev = NotifyPropertyChanged;
if (ev != null)
    ev(this, new PropertyChangedEventArgs("Foo"));

Но что если произойдет следующая ситуация:
1) подписываюсь на слушателя:

mytype.NotifyPropertyChanged += Handler; // Handler is instance method in SomeObject class

2) Я (или среда выполнения, из-за ограниченности) располагаю SomeObject, который содержит прослушиватель и отменяет подписку прослушивателя, примерно в то же время, когда происходит уведомление свойства.

3) Хотя вряд ли из-за очень короткого периода времени это может произойти, теоретически возможно, что, поскольку ev сохраняет старого абонента, который больше не существует, он вызовет функцию в объекте которые больше не существуют.

По словам Эрика Липперта, «обработчики событий должны быть устойчивыми перед вызовом даже после того, как событие было отписано ». Но если обработчик отписался и утилизировал , он больше не может заботиться о вызове. Как правильно справиться с этой ситуацией?

Свернуть код из (1) в try-catch? Какое исключение следует поймать? Кажется вероятным, что ObjectDisposedException, но не единственный, который может произойти.

Ответы [ 5 ]

10 голосов
/ 01 декабря 2011

Я полагаю, что вы хотели сказать объект, который уже был GC'd, а не утилизирован. Ну, это не может произойти; MultiCastDelegate (EventHandler) поддерживает ссылку на объект через его подписанный метод, то есть он не может быть GC, пока не будет удален обработчик.


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

Сам объект все еще жив и здоров, хотя он может вызвать исключение, если вы вызовете метод, который зависел от этого собственного ресурса (конечно, зависит от реализации. Дело в том, что объект все еще существует, как и метод).

Ничего волшебного не происходит, когда вы звоните Dispose(). Я мог бы легко создать класс, который реализует IDisposable и имеет полностью пустой метод Dispose(). Называйте все как хотите, это ничего не делает и никак не меняет состояние объекта.

2 голосов
/ 01 декабря 2011

Все, что сказал Эд С., верно. Я хочу пояснить, что поведение, которое вы увидите, будет зависеть от реализации обработчика функции / события. Это может или не может вызвать исключение, и это может или не может вести себя странно.

Уничтожение не делает ничего магического, что сделало бы объект недоступным. Обычно Dispose просто выполняет некоторые действия, например, если бы одно из полей / свойств было дескриптором файла, тогда он по очереди вызывает dispose для этого дескриптора файла, чтобы немедленно освободить ресурс (см. http://blogs.msdn.com/b/kimhamil/archive/2008/11/05/when-to-call-dispose.aspx)

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

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

Также возможно, что человек, который написал этот класс / функцию, имеет явный if(Disposed){throw blah; }, так что, если вы попытаетесь вызвать функцию, он выдаст исключение, сообщающее, что операция в удаленном экземпляре недопустима.

Важно отказаться от подписки на события, иначе ваш объект никогда не будет GC'd.

1 голос
/ 01 декабря 2011

Этот код всегда работает ...

var ev = NotifyPropertyChanged;
if (ev != null)     
    ev(this, new PropertyChangedEventArgs("Foo")); 

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

1 голос
/ 01 декабря 2011

Задача объекта - решить, что должно произойти, если он уже был удален (он все еще может выполнять код в состоянии «уничтожен», поскольку Dispose - это просто то, что вы добровольно вызываете, чтобы сообщить объекту о необходимости очиститься). видел эту причину ошибки во время завершения работы приложения, когда события обрабатывались в другом потоке, пока объект очищался; обработчики событий не были написаны должным образом, чтобы предотвратить это состояние гонки. Стоит остерегаться.

1 голос
/ 01 декабря 2011

Я считаю, что в большинстве ситуаций вам не следует ловить какие-либо исключения, выдаваемые подписчиками.От вас действительно нельзя ожидать обработки всех возможных исключений, которые могут быть сгенерированы клиентским кодом.Короче говоря, я думаю, что здесь должен применяться принцип быстрого отказа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...