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

Является ли реализация ниже потокобезопасной? Если нет, то что мне не хватает? Мне где-нибудь нужны 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);
    }
}

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


person Svish    schedule 24.06.2009    source источник


Ответы (2)


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

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

person Marc Gravell    schedule 24.06.2009
comment
Добавил, почему я использовал частный обработчик событий и явный материал событий в свой вопрос. Это еще не нужно? И что вы имеете в виду под этой разницей? Автоматически блокирует ли общедоступное событие EventHandler ‹ProcessCompletedEventArgs› SomeEvent? - person Svish; 24.06.2009
comment
Да; полевые события (т.е. событие без явного добавления / удаления) имеют встроенную блокировку (this); см. 10.8.1 в спецификации языка (версия MS); однако это обходит код внутри класса - см. marcgravell.blogspot.com/2009/02/; поэтому как событие private добавление / удаление (и, следовательно, блокировка) никогда не используются. Для явной реализации интерфейса код в порядке, и вам нужно будет добавить блокировку самостоятельно, что вы и сделали - и, возможно, лучше, чем блокировка (this). Придерживайтесь этого ;-p - person Marc Gravell; 24.06.2009

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

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

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

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

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

person Jon Skeet    schedule 24.06.2009
comment
Вы уверены, что блокировка необходима? Делегаты неизменяемы, а assignmend - это атомарная операция, поэтому я думаю, блокировка не требуется. - person TcKs; 24.06.2009
comment
Смотрите мои комментарии к вашему сообщению. Вам абсолютно необходима блокировка, чтобы сделать его поточно-ориентированным. - person Jon Skeet; 24.06.2009
comment
Да, блокировка в add & remove необходима. Но какие преимущества я получил от блокировки в OnProcessingCompleted? - person TcKs; 24.06.2009
comment
Без блокировки нет барьера памяти, поэтому нет гарантии, что вы увидите последнее значение. См. pobox.com/~skeet/csharp/threads/volatility.shtml. - person Jon Skeet; 24.06.2009
comment
Даже с блокировкой нет гарантии, что вы видите последнее значение, потому что это зависит от того, кто будет первым: чейнджер или вызывающий. Поэтому вам нужны только блокировки для добавления / удаления, потому что оба являются двумя этапами в памяти: чтение и запись Таким образом, между чтением и записью другой чейнджер может читать и писать, поэтому чейнджер 1 перезаписывает изменения чейнджера 2. Но это не беспокоит вызывающего. Он может читать между чтением и записью чейнджера, но я не вижу в этом никаких проблем. (Вы не можете сказать, какое значение получает вызывающий, если оба работают более или менее одновременно; с блокировкой или без нее) - person mmmmmmmm; 24.06.2009
comment
будет var handler = Interlocked.CompareExchange(ProcessCompleted, null, null); делать? ` - person bohdan_trotsenko; 29.08.2012