Предлагаемый метод расширения Cerating на самом деле не решает проблемы с условиями гонки, а скорее скрывает их.
public static void SafeInvoke(this EventHandler handler, object sender)
{
if (handler != null) handler(sender, EventArgs.Empty);
}
Как указано, этот код является элегантным эквивалентом решения с временной переменной, но ...
Проблема с обоими заключается в том, что возможно, что подписчик события может быть вызван ПОСЛЕ того, как он отписался от события . Это возможно, потому что отмена подписки может произойти после того, как экземпляр делегата будет скопирован во временную переменную (или передан как параметр в методе выше), но до вызова делегата.
В целом поведение клиентского кода в таком случае непредсказуемо: состояние компонента уже не могло позволить обрабатывать уведомление о событии. Можно написать клиентский код так, чтобы он справлялся с этим, но это возложит на клиента ненужную ответственность.
Единственный известный способ обеспечить безопасность потока - использовать инструкцию блокировки для отправителя события. Это гарантирует, что все подписки \ отписки \ вызовы будут сериализованы.
Чтобы быть более точным, блокировку следует применить к тому же объекту синхронизации, который используется в методах доступа к событию add \ remove, который по умолчанию является this.