Проверка на нулевое значение перед отправкой события ... Поток безопасно? - PullRequest
35 голосов
/ 12 ноября 2008

Что-то, что меня смущает, но никогда не вызывало проблем ... рекомендуемый способ отправки события следующий:

public event EventHandler SomeEvent;
...
{
    ....
    if(SomeEvent!=null)SomeEvent();
}

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

Ответы [ 6 ]

55 голосов
/ 12 ноября 2008

Как вы указали, когда несколько потоков могут одновременно обращаться к SomeEvent, один поток может проверить, является ли SomeEvent нулевым, и определить, что это не так. Сразу после этого другой поток может удалить последнего зарегистрированного делегата из SomeEvent. Когда первый поток попытается поднять SomeEvent, будет выдано исключение. Разумный способ избежать этого сценария:

protected virtual void OnSomeEvent(EventArgs args) 
{
    EventHandler ev = SomeEvent;
    if (ev != null) ev(this, args);
}

Это работает, потому что всякий раз, когда делегат добавляется или удаляется из события с использованием реализаций добавления и удаления по умолчанию, используются статические методы Delegate.Combine и Delegate.Remove. Каждый из этих методов возвращает новый экземпляр делегата, а не изменяет переданный ему.

Кроме того, для ссылки на объект в .NET задано atomic , а реализациями доступа по умолчанию для добавления и удаления по умолчанию являются synchronized . Таким образом, приведенный выше код успешно выполняет сначала копирование многоадресного делегата из события во временную переменную. Любые изменения в SomeEvent после этого момента не повлияют на копию, которую вы сделали и сохранили. Таким образом, теперь вы можете безопасно проверить, зарегистрированы ли какие-либо делегаты, и впоследствии вызывать их.

Обратите внимание, что это решение решает одну проблему гонки, а именно проблему обработчика события, равного нулю при его вызове. Это не решает проблему, когда обработчик события перестает функционировать при его вызове, или обработчик события подписывается после получения копии.

Например, если обработчик события зависит от состояния, которое уничтожается, как только отменяется подписка на обработчик, тогда это решение может вызвать код, который не может работать должным образом. См. превосходную запись в блоге Эрика Липперта для получения более подробной информации. Также см. этот вопрос StackOverflow и ответы .

РЕДАКТИРОВАТЬ: Если вы используете C # 6.0, то Ответ Кшиштофа выглядит хорошим способом.

23 голосов
/ 06 сентября 2015

В C # 6.0 вы можете использовать монадический нулевой условный оператор ?. для проверки на нулевое значение и генерирования событий простым и поточно-ориентированным способом.

SomeEvent?.Invoke(this, args);

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

22 голосов
/ 12 ноября 2008

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

public event EventHandler SomeEvent = delegate {};

Смежный вопрос: Есть ли минус в добавлении анонимного пустого делегата в объявление события?

4 голосов
/ 12 ноября 2008

Рекомендуемый способ немного отличается и использует временное значение следующим образом:

EventHandler tmpEvent = SomeEvent;
if (tmpEvent != null)
{
    tmpEvent();
}
3 голосов
/ 12 ноября 2008

Безопасный подход:


public class Test
{
    private EventHandler myEvent;
    private object eventLock = new object();

    private void OnMyEvent()
    {
        EventHandler handler;

        lock(this.eventLock)
        {
            handler = this.myEvent;
        }
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }

    public event MyEvent
    {
        add
        {
            lock(this.eventLock)
            {
                this.myEvent += value;
            }
        }
        remove
        {
            lock(this.eventLock)
            {
                this.myEvent -= value;
            }
        }

    }
}

-Билль

0 голосов
/ 08 декабря 2014

Я хотел бы предложить небольшое улучшение ответа RoadWarrior от использование функции расширения для EventHandler:

public static class Extensions
{
    public static void Raise(this EventHandler e, object sender, EventArgs args = null)
    {
        var e1 = e;

        if (e1 != null)
        {
            if (args == null)
                args = new EventArgs();

            e1(sender, args);
        }                
    }
  }

С этим расширением, события могут быть вызваны просто:

класс SomeClass { публичное событие EventHandler MyEvent;

void SomeFunction()
{
    // code ...

    //---------------------------
    MyEvent.Raise(this);
    //---------------------------
}

}

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