Вопрос по .net-4.0, multithreading, c# – Поток безопасных событий

9

Обычная практика, позволяющая избегать состояния гонки (в многопоточных приложениях) при запуске событий, заключается в следующем:

EventHandler<EventArgs> temp = SomeEvent;
if (temp != null) temp(e);

"Remember that delegates are immutable and this is why this technique works in theory. However, what a lot of developers don't realize is that this code could be optimized by the compiler to remove the local temp variable entirely. If this happens, this version of the code is identical to the first version, so a NullReferenceException is still possible."

Проблема (согласно книге) заключается в том, что «этот код может быть оптимизирован компилятором для полного удаления локальной временной переменной. Если это произойдет, эта версия кода будет идентична первой версии, поэтому исключение NullReferenceException все еще возможно & quot;

Согласно CLR через C #, здесь есть лучший способ заставить компилятор скопировать указатель на событие.

virtual void OnNewMail(NewMailEventArgs e)
{
    EventHandler<NewMailEventArgs> temp =
                          Interlocked.CompareExchange(ref NewMail, null, null);
    if (temp != null) 
        temp(this, e);
}

Here, CompareExchange changes the NewMail reference to null if it is null and does not alter NewMail if it is not null. In other words, CompareExchange doesn't change the value in NewMail at all, but it does return the value inside NewMail in an atomic, thread-safe way. Richter, Jeffrey (2010-02-12). CLR via C# (p. 265). OReilly Media - A. Kindle Edition.

Я на платформе .Net 4.0, и не уверен, как это может работать, потому что Interlocked.CompareExchange ожидает ссылку на местоположение, а не ссылку на событие.

Либо в книге есть ошибка, либо я ее неправильно истолковал. Кто-нибудь реализовал этот метод? Или есть лучший способ предотвратить гонки здесь?

ОБНОВИТЬ

это была моя ошибка, замкнутый код работает. я просто указал неправильное приведение, но согласно Брэдли (ниже) это не обязательно в .net 2.0 и выше на окнах.

blogs.msdn.com/b/ericlippert/archive/2009/04/29/… может быть интересно Vlad

Ваш Ответ

4   ответа
0

то увидите, что метод вызывается так

IL_000d:  ldsflda    class [mscorlib]System.EventHandler`1<class [mscorlib]System.EventArgs> ConsoleApplication1.Program::MyEvent
IL_0012:  ldnull
IL_0013:  ldnull
IL_0014:  call       !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.EventHandler`1<class [mscorlib]System.EventArgs>>(!!0&,!!0,!!0)

видеть, чтоldsflda - мое событие статично, но оно загружает адресfield, Это поле является автоматически сгенерированным полем делегата, которое компилятор генерирует для каждого события.

поле определяется так:

.field private static class [mscorlib]System.EventHandler`1<class [mscorlib]System.EventArgs> MyEvent
1

вы не поняли истолковано. Местоположение означает просто указатель на ссылку на объект [msdn version:The destination object that is compared with comparand and possibly replaced.]. Следующий код отлично работает в .NEt 4.0

public class publisher
{

    public event EventHandler<EventArgs> TestEvent;
    protected virtual void OnTestEvent(EventArgs e)
    {
        EventHandler<EventArgs> temp = Interlocked.CompareExchange(ref TestEvent, null, null);
        if (temp != null)
            temp(this,e);
    }
}
8

if/temp прочь (в CLR 2.0 и позже);Модель памяти CLR 2.0 не позволяет вводить чтение из кучи (правило № 2).

Таким образом,MyEvent не может быть прочитан во второй раз; значениеtemp должны быть прочитаны вif заявление.

Увидетьмой блог для расширенного обсуждения этой ситуации и объяснения того, почему стандартная модель в порядке.

Однако, если вы работаете в CLR не от Microsoft (например, mono), который не обеспечивает гарантии модели памяти CLR 2.0 (но только следует модели памяти ECMA), или вы работаете на Itanium (который имеет общеизвестный характер) слабая аппаратная модель памяти), вам потребуется код, такой как Рихтер, для устранения возможного состояния гонки.

Что касается вашего вопроса оInterlocked.CompareExchangeСинтаксисpublic event EventHandler<NewMailEventArgs> NewMail это просто синтаксический сахар C # для объявления частного поля типаEventHandler<NewMailEventArgs> и публичное мероприятие, которое имеетadd а такжеremove методы.Interlocked.CompareExchange вызов читает значение частногоEventHandler<NewMailEventArgs> поле, поэтому этот код компилируется и работает так, как описывает Рихтер; в Microsoft CLR это просто не нужно.

@BradleyGrainger, значит, вы говорите, что самый первый пример вполне подходит, или вы все равно должны использовать Interlocked (на окнах)? Sonic Soul
Только в Windows / .Net complier / JIT это не так для Mono.ecma-international.org/publications/standards/Ecma-335.htm
@SonicSoul: Да, я прямо противоречу Рихтеру. IIRC,CLR via C# это книга, где он утверждал, что призваниеIDisposable.Dispose в этом нет необходимости, и я тоже полностью с этим не согласен. :-)
я обновил мой пример с комментариями от автора. Если то, что вы говорите, верно, это прямо противоречит тому, что говорит Джеффри Рихтер. И я поймал его на неточных утверждениях в этой книге, прежде чем ... я просто хотел бы подтвердить правильный способ сделать это .. Sonic Soul
@caesay: Хороший вопрос; Я упоминал об этом в своем блоге, но не в ответе; Я обновлю.
4

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

The problem is that the compiler may optimize that if/temp away,

Ну, согласноCLR via C# (стр. 264 & # x2013; 265)

[The] code could be optimized by the compiler to remove the local […] variable entirely. If this happens, this version of the code is identical to the [version that references the event twice], so a NullReferenceException is still possible.

Так что, этоpossibleоднако важно знать, что компилятор Microsoft JIT (Just-in-Time) не выполняетever оптимизировать локальную переменную. Хотя это может измениться, это маловероятно, потому что это, вероятно, сломает много приложений.

Это потому, что .Net имеет сильную модель памяти:http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S5

Reads and writes cannot be introduced.

а также

The model does not allow reads to be introduced, however, because this would imply refetching a value from memory, and in low-lock code memory could be changing.

& # XA0;

Тем не менее, Mono, который следует загораздо более слабая модель памяти, может оптимизировать эту локальную переменную.

Итог: если вы не планируете использовать Mono, не беспокойтесь об этом.

И даже тогда это поведение может быть подавлено изменчивыми декларациями.

вы правы ... я тут же пришел к выводу. скорректировал мой пост и удалил "оптимизированный прочь" заявление. Sonic Soul

Похожие вопросы