События и многопоточный код

Недавно, просматривая главу книги Framework Design Guidelines, посвященную событиям (event) наткнулся на интересное замечание. Это же замечание про события процитировано и на блоге Brad Abrams Events, Delegate and Multithreading . Суть его в следующем. Предположим, что у нашего класса есть некое событие Clicked, объявленное следующим образом:
    public event EventHandler Clicked;
Если метод генерации события написан в виде:
    protected void OnUnsafeRaiseClick()
{
if (Clicked != null) Clicked(this, EventArgs.Empty);
}
- это приводит к небезопасному поведению в случае многопоточного исполнения, когда один поток пытается сгенерировать событие, а второй в этот момент отписывает последний делегат. При определенном стечении обстоятельств проверка
    Clicked != null
может пройти успешно (второй поток еще не отписал последний делегат), а попытка выполнить
    Clicked(this, EventArgs.Empty)
может привести к исключению System.NullReferenceException (если второй поток уже отписал последнее событие). Рекомендуемым способом генерации события будет следующий:
    protected void OnSafeRaiseClick()
{
EventHandler handler = Clicked;
if (handler != null)
handler(this, EventArgs.Empty);
}
По правде говоря, мне было совсем не очевидно, почему копирование ссылки делает исполнение кода безопасным в многопоточной среде, а поиск в Google конкретно по этому вопросу ничего не дал, поэтому провел свое маленькое исследование.
Немного теории. Если вы не очень хорошо знаете внутреннюю кухню событий и делегатов, то предварительно рекомендую просмотреть статью Делегаты и события. Как известно, подписка и отписка от события при помощи C#-операторов += и -= приводит к вызову статических методов Delegate.Combine и Delegate.Remove соответственно. Работа этих методов на наследниках от MulticastDelegate приводит к тому, что будет создан новый делегат, с новым списком вызовов (invocation list), а старый будет собран сборщиком мусора (если на него нет ссылок).
Вернемся теперь к интересной ситуации, когда один поток пытается исполнить метод OnSafeRaiseClick, а второй поток отписывает делегаты. После исполнения первым потоком кода
    EventHandler handler = Clicked
модификация вторым потоком Clicked приведет к созданию нового делегата с новым списком вызовов и событие Clicked будет хранить ссылку на новый делегат или null (если отписали последнее событие), а handler будет хранить ссылку на первоначальный делегат. Далее исполнение первым потоком
    if (handler != null)
handler(this, EventArgs.Empty);
будет вестись над исходным делегатом и проблемы не возникнет.
Интересен тот факт, что дополнительных накладных расходов в методе OnSafeRaiseClick по сравнению с OnUnsafeRaiseClick при однопоточном исполнении очень немного.

Комментарии

Unknown написал(а)…
Вот все не могу понять: как вы добавляете код да еще с подцветкой синтаксиса? сам запарился...
Андрей Бороздин написал(а)…
Точно так же парюсь :). А судя по твоему блогу у тебя добавлять код в статьи тоже очень не плохо получается.
Unknown написал(а)…
По дуратцкой привычке сначала спросил потом поискал ))

Вот классная вещь http://quickhighlighter.com/code-syntax-highlighter.php

неудобно лишь то, что надо потом смотреть исходник страницы и выковыривать html-код.

Популярные сообщения из этого блога

Команды docker save/load, docker export/import – в чем отличие, как и когда ими пользоваться

Как узнать, кто заблокировал файл или папку

Эрик Гамма переходит на работу в Майкрософт