Создание событий в многопоточной среде [дубликат]


На этот вопрос уже есть ответ здесь:

Начиная с .NET 4.0 автоматически генерируемые обработчики событий add/remove являются потокобезопасными (здесь и здесь). Поэтому клиенты, которые регистрируют своих слушателей в открытом событии, могут делать это одновременно из нескольких потоков без гонок.

Но что, если я хочу запустить событие потокобезопасным способом? Рекомендуемая практика, по-видимому, заключается в следующем (здесь):

public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
    EventHandler myEvent = MyEvent;
    if (myEvent != null)
    {
        myEvent(this, e);
    }
}

Однако, прочитав что-то о модели памяти .NET (например, журнал MSDN2012-12 и еще 2013-01) я больше не думаю, что это правильно. Меня беспокоит, что чтение памяти может быть введено компилятором, и поэтому приведенный выше код может быть преобразован в нечто подобное:

public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
    // JIT removed the local variable and introduced two memory reads instead.
    if (MyEvent != null)
    {
        // A race condition may cause the following line to throw a NullReferenceException.
        MyEvent(this, e);
    }
}

Законно: удалите локальную переменную и используйте повторные операции чтения памяти, так как это не изменяет поведение метода, если он выполняется в однопоточной среде. Это по спецификации ECMA (ECMA-335: I. 12.6.4). Понятный пример также приводится в 2013-01 выпуск журнала MSDN.

Я что-то упустил? Если нет, то, пожалуйста, посоветуйте обходной путь.

1 4

1 ответ:

Вы должны добавить единственную строку, чтобы сделать первый фрагмент правильным в многопоточной среде:

public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
    EventHandler myEvent = MyEvent;
    Thread.MemoryBarrier();
    if (myEvent != null)
    {
        myEvent(this, e);
    }
}

Барьер памяти отказывается переупорядочивать чтение и запись как для компилятора, так и для процессора. Именно так реализуются операции чтения/записи с изменяющимися параметрами. Вы можете прочитать больше о барьере памяти здесь.