Подтвердить что ты не робот

Проверка на нуль до отправки события... потокобезопасная?

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

Например, если обработчик события зависит от состояния, которое уничтожается, как только обработчик не подписан, тогда это решение может вызывать код, который не может работать должным образом. Подробнее см. Eric Lippert отличную запись в блоге. Также см. fooobar.com/questions/4545/....

EDIT: если вы используете С# 6.0, то ответ Krzysztof выглядит как хороший способ.

Ответ 2

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

public event EventHandler SomeEvent = delegate {};

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

Ответ 3

В С# 6.0 вы можете использовать monadic Null-условный оператор ?. для проверки событий с нулевым и повышающим эффектом простым и потокобезопасным способом.

SomeEvent?.Invoke(this, args);

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

Ответ 4

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

EventHandler tmpEvent = SomeEvent;
if (tmpEvent != null)
{
    tmpEvent();
}

Ответ 5

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


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;
            }
        }

    }
}

-Билль

Ответ 6

Я хотел бы предложить небольшое улучшение ответа 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);
    //---------------------------
}

}