С#: потокобезопасные события
Является ли реализация ниже потокобезопасной? Если нет, чего я не хватает? Должен ли я иметь ключевые слова 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
.
Не так много можно сделать по этому поводу, кроме того, что сам хендлер знал, что его больше нельзя вызывать.
Возможно, лучше просто не пытаться сделать потоки потокобезопасными - указать, что подписка должна только изменяться в потоке, который поднимет событие.