События на самом деле являются синтаксическим сахаром над списком делегатов. Когда вы вызываете событие, он действительно перебирает этот список и вызывает каждого делегата с параметрами, которые вы передали.
Проблема с потоками заключается в том, что они могут добавлять или удалять элементы из этой коллекции путем подписки / отказа от подписки. Если они сделают это во время итерации коллекции, это вызовет проблемы (я думаю, что возникает исключение)
Цель состоит в том, чтобы скопировать список перед его повторением, чтобы вы были защищены от изменений в списке.
Примечание. Однако теперь ваш слушатель может быть вызван даже после того, как вы отменили подписку, поэтому вам следует убедиться, что вы обработали это в своем коде слушателя.
Лучшая практика - вторая форма. Причина в том, что другой поток может обнулить или изменить SomeEvent
между тестом « if
» и вызовом.
Здесь хорошая статья о событиях .NET и условиях гонки с потоками. Он охватывает некоторые распространенные сценарии и содержит несколько полезных ссылок.
Надеюсь, это поможет.
ИМО, другие ответы упускают одну ключевую деталь - делегаты (и, следовательно, события) неизменяемы . Смысл этого в том, что подписка или отмена подписки на обработчик событий не просто добавляет / удаляет список - скорее, он заменяет список новым с дополнительным (или одним предметом меньше) на нем.
Поскольку ссылки являются атомарными, это означает, что в момент, когда вы делаете:
var handler = SomeEvent;
, теперь у вас есть жесткий экземпляр, который не может измениться, даже если в следующую пикосекунду другой поток отменит подписку (вызывая ] фактическое поле события становится нулевым
).
Итак, вы проверяете значение null и вызываете его, и все в порядке. Обратите внимание, конечно, что существует все еще запутанный сценарий события, вызванного на объекте, который думает, что он отписался от подписки пикосекунду назад!