Как реализовать поточно-безопасный безошибочный обработчик событий на C #?

Предпосылки проблемы

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

Это можно легко выполнить с помощью такого кода:

public event EventHandler MyEvent;
public void RaiseEventSafely( object sender, EventArgs e )
{
    foreach(EventHandlerType handler in MyEvent.GetInvocationList())
        try {handler( sender, e );}catch{}
}


Общее, поточно-безопасное, безошибочное решение

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

Я решил реализовать это как общий класс, в котором общий тип ограничен пункт «where», чтобы быть делегатом. Я действительно хотел, чтобы ограничение было «делегатом» или «событием», но они недействительны, поэтому использование Delegate в качестве ограничения базового класса - лучшее, что я могу сделать. Затем я создаю объект блокировки и блокирую его в методах добавления и удаления общедоступного события, которые изменяют частную переменную делегата с именем «event_handlers».

public class SafeEventHandler<EventType> where EventType:Delegate
{
    private object collection_lock = new object();
    private EventType event_handlers;

    public SafeEventHandler(){}

    public event EventType Handlers
    {
        add {lock(collection_lock){event_handlers += value;}}
        remove {lock(collection_lock){event_handlers -= value;}}
    }

    public void RaiseEventSafely( EventType event_delegate, object[] args )
    {
        lock (collection_lock)
            foreach (Delegate handler in event_delegate.GetInvocationList())
                try {handler.DynamicInvoke( args );}catch{}
    }
}


Проблема компилятора с оператором + =, но два простых решения.

Одна из проблем заключалась в том, что строка «event_handlers + = value;» приводит к ошибке компилятора «Оператор '+ =' не может быть применен к типам 'EventType' и 'EventType'». Несмотря на то, что EventType должен быть типом делегата, он не позволяет использовать для него оператор + =.

В качестве обходного пути я просто добавил ключевое слово event в «event_handlers», поэтому определение выглядит так: « частное событие EventType event_handlers; », и это прекрасно компилируется. Но я также подумал, что, поскольку ключевое слово "event" может генерировать код для обработки этого, я тоже должен быть в состоянии, поэтому я в конечном итоге изменил его на это, чтобы избежать неспособности компилятора распознать, что '+ =' ДОЛЖЕН применяться к универсальный тип, ограниченный делегатом. Частная переменная event_handlers теперь набирается как Delegate вместо общего EventType, а методы добавления / удаления следуют этому шаблону event_handlers = MulticastDelegate.Combine (event_handlers, value);


Окончательный код выглядит следующим образом:

public class SafeEventHandler<EventType> where EventType:Delegate
{
    private object collection_lock = new object();
    private Delegate event_handlers;

    public SafeEventHandler(){}

    public event EventType Handlers
    {
        add {lock(collection_lock){event_handlers = Delegate.Combine( event_handlers, value );}}
        remove {lock(collection_lock){event_handlers = Delegate.Remove( event_handlers, value );}}
    }

    public void RaiseEventSafely( EventType event_delegate, object[] args )
    {
        lock (collection_lock)
            foreach (Delegate handler in event_delegate.GetInvocationList())
                try {handler.DynamicInvoke( args );}catch{}
    }
}


Вопрос

У меня вопрос ... похоже, это хорошо работает? Есть ли способ лучше, или в основном так и должно быть? Думаю, я исчерпал все варианты. Использование блокировки в методах добавления / удаления общедоступного события (поддерживаемых частным делегатом), а также использование той же блокировки при выполнении списка вызовов - единственный способ сделать список вызовов потокобезопасным, а также обеспечить ошибки, создаваемые обработчиками, не мешают вызову других обработчиков.

6
задан Triynko 28 June 2011 в 21:32
поделиться