Добавить делегата на событие - безопасность потока

Можно выполнить следующий код из нескольких потоков одновременно.

this._sequencer.Completed += OnActivityFinished;

Безопасно ли добавлять потоки в обработчик событий из нескольких потоков?

Безопасен ли поток для удаления делегата из обработчика событий из нескольких потоков?

Какой самый простой и понятный способ сделать этот поток безопасным?

3 ответа

Решение

Если вы не укажете свои собственные обработчики добавления / удаления событий, компилятор C# сгенерирует этот обработчик добавления (реконструированный с помощью .NET Reflector):

public void add_MyEvent(EventHandler value)
{
    EventHandler handler2;
    EventHandler myEvent = this.MyEvent;
    do
    {
        handler2 = myEvent;
        EventHandler handler3 = (EventHandler) Delegate.Combine(handler2, value);
        myEvent = Interlocked.CompareExchange<EventHandler>(ref this.MyEvent, handler3, handler2);
    }
    while (myEvent != handler2);
}

и обработчик удаления, который выглядит так же, но с Delegate.Remove вместо Delegate.Combine,

Обратите внимание на использование Interlocked.CompareExchange? Это предотвращает состояние гонки между обновлением поля поддержки события и чтением из него. Таким образом, это потокобезопасно.

Это зависит от реализации мероприятия, если честно.

Подобные полю события, генерируемые компилятором C#, являются поточно-ориентированными, но если это пользовательское событие, кто знает?

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

Для полевых событий добавление / удаление обработчиков поточно-ориентировано. Из спецификации:

При компиляции события, подобного полю, компилятор автоматически создает хранилище для хранения делегата и создает методы доступа для события, которые добавляют или удаляют обработчики событий в поле делегата. Чтобы быть потокобезопасным, операции добавления или удаления выполняются, удерживая блокировку (§8.12) на содержащем объекте для события экземпляра, или объект типа (§7.6.10.6) для статического события.

Однако это верно для C# 3.0 и менее, в C# 4.0 компилятор генерирует реализацию без блокировки с использованием подпрограмм Interlocked (но спецификация остается той же - ошибка?)

В пользовательских реализациях никто не может сказать точно... кроме, возможно, автора кода:)

Другие вопросы по тегам