С#: потокобезопасные события

Является ли реализация ниже потокобезопасной? Если нет, чего я не хватает? Должен ли я иметь ключевые слова volatile где-то? Или замок где-нибудь в методе OnProcessingCompleted? Если да, то где?

public abstract class ProcessBase : IProcess
{
    private readonly object completedEventLock = new object();

    private event EventHandler<ProcessCompletedEventArgs> ProcessCompleted;

    event EventHandler<ProcessCompletedEventArgs> IProcess.ProcessCompleted
    {
        add
        {
            lock (completedEventLock)
                ProcessCompleted += value;
        }
        remove
        {
            lock (completedEventLock)
                ProcessCompleted -= value;
        }
    }

    protected void OnProcessingCompleted(ProcessCompletedEventArgs e)
    {
        EventHandler<ProcessCompletedEventArgs> handler = ProcessCompleted;
        if (handler != null)
            handler(this, e);
    }
}

Примечание. Причина, по которой у меня есть частное событие и явный материал интерфейса, объясняется тем, что это абстрактный базовый класс. И классы, которые наследуют от него, не должны делать ничего с этим событием напрямую. Добавлена ​​оболочка класса, чтобы было более ясно =)

Ответ 1

Нет необходимости, чтобы частный член ProcessCompleted был event - он мог просто быть полем: private EventHandler<ProcessCompletedEventArgs> ProcessCompleted; - внутри класса он всегда идет прямо в поле, поэтому материал event все равно потерял.

Подход, который вы показали с явным объектом блокировки, не намного более потокобезопасен, чем просто полевое событие (т.е. public event EventHandler<ProcessCompletedEventArgs> ProcessCompleted; - единственное различие заключается в том, что вы не блокируете "this" ( это хорошая вещь - в идеале вы должны избегать блокировки на this). Подход "обработчик переменной" является правильным, но есть еще side -эффекты, о которых вы должны знать.

Ответ 2

Вам нужно заблокировать, когда вы также получите обработчик, иначе вы можете не иметь последнее значение:

protected void OnProcessingCompleted(ProcessCompletedEventArgs e)
{
    EventHandler<ProcessCompletedEventArgs> handler;
    lock (completedEventLock) 
    {
        handler = ProcessCompleted;
    }
    if (handler != null)
        handler(this, e);
}

Обратите внимание, что это не мешает условию гонки, когда мы решили, что мы выполним набор обработчиков, а затем один обработчик не будет подписан. Он все равно будет вызван, потому что мы получили делегат многоадресной передачи, содержащий его в переменной handler.

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

Возможно, лучше просто не пытаться сделать потоки потокобезопасными - указать, что подписка должна только изменяться в потоке, который поднимет событие.