Добавить делегата на событие - безопасность потока
Можно выполнить следующий код из нескольких потоков одновременно.
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 (но спецификация остается той же - ошибка?)
В пользовательских реализациях никто не может сказать точно... кроме, возможно, автора кода:)