EventHandler <TEventArgs> безопасность потоков в С#?

Использование myusus EventArgs, например:

public event EventHandler<MyEventArgs> SampleEvent;

из msdn, например:

public class HasEvent
{
// Declare an event of delegate type EventHandler of 
// MyEventArgs.

    public event EventHandler<MyEventArgs> SampleEvent;

    public void DemoEvent(string val)
    {
    // Copy to a temporary variable to be thread-safe.
        EventHandler<MyEventArgs> temp = SampleEvent;
        if (temp != null)
            temp(this, new MyEventArgs(val));
    }
}

У меня вопрос 2:

1) глядя на отмеченный код:

enter image description here

Я не вижу причины, почему он должен быть скопирован в другой параметр (относительно потоков)

так как у нас есть event keyowrd, никто не может коснуться своего списка вызовов (никакого внешнего кода для класса, который я имею в виду)

2) Если im не ошибается, функция DemoEvent должна быть виртуальной, поэтому ее можно переопределить в подклассах... (im sure ive видел ее где-то)

странно, что resharper также не добавит виртуального:

поэтому, если у меня есть этот код:

enter image description here

это говорит мне:

enter image description here

и когда я нажимаю его:

enter image description here

так снова мои 2 вопросы:

1), каков сценарий, который эта строка EventHandler<MyEventArgs> temp = SampleEvent; будет решать, касаясь потока safty?

2) не должна ли функция быть virtual? (im im ive видел этот шаблон с виртуальным)

Ответ 1

каков сценарий этой строки EventHandler temp = SampleEvent; будет решать, касаясь потока safty?

Представьте, что вы это делаете:

if (SampleEvent != null)
    SampleEvent(this, new MyEventArgs());

Если другой поток отсоединит обработчик событий после if, но до вызова, вы попытаетесь вызвать делегат null (и вы получите исключение).

не должна ли функция быть виртуальной? (im im ive видел этот шаблон с виртуальным)

Да, если класс не является sealed, тогда вы должны пометить эту функцию virtual (это не обязательно, но это хорошо принятый шаблон).

ИЗМЕНИТЬ

Time  Thread 1                                      Thread 2
1                                                   obj.SampleEvent += MyHandler;
2      if (SampleEvent != null)                     
3      {                                            obj.SampleEvent -= MyHandler;
4          SampleEvent(this, new MyEventArgs());
5      }

В этом случае в момент 4 вы выберете делегат null, и он выкинет NullReferenceException. Теперь посмотрите этот код:

Time  Thread 1                                      Thread 2
1                                                   obj.SampleEvent += MyHandler;
2      var sampleEvent = SampleEvent;
3      if (sampleEvent != null)                     
4      {                                            obj.SampleEvent -= MyHandler;
5          sampleEvent(this, new MyEventArgs());
6      }

Теперь в момент 5 вы вызываете sampleEvent, и он содержит старый контент sampleEvent, в этом случае он не будет вызывать никаких исключений (но он вызовет MyHandler even если он был удален вторым потоком).

Ответ 2

 if (SampleEvent != null)
    SampleEvent(this, new MyEventArgs(val));

Это классическая гонка. Другой поток может отменить подписку на обработчик событий во время выполнения этого кода. Который делает оператор if() заключить, что абонент, кроме вызова события, терпит неудачу с исключением NullReferenceException. Копирование объекта делегата в локальную переменную гарантирует, что код клиента, изменяющий ссылку на объект делегата, отписавшись от обработчика события, не приведет к сбою. Все еще проблема, вы вызовете обработчик события после того, как он был отписано, но это неизбежная гонка и не обязательно фатальная, как NRE, и может быть обработана обработчиком событий, в отличие от NRE.

Да, такой метод обычно делается защищенным виртуальным и называется OnSampleEvent(), поэтому производный класс может изменять поведение повышения событий.