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