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

в 17:43, , рубрики: Песочница, метки: ,

Данная заметка — попытка вставить свои 5 копеек на тему, затронутую в статье пользователя KumoKairo.

Не далее как год назад я столкнулся с подобной проблемой — необходимо было рассылать события клиентам из WCF-службы, причем один подписчик события не должен страдать от того, что другой подписчик события в момент генерации этого события отвалился.

Как и автор упомянутой заметки, я пошел смотреть, что же такого умного написано на эту тему, и набрел вот на эту публикацию. Пришел к выводу, что строго говоря, задача не решается, то есть мы никак не сможем избежать случайного вызова обработчика событий от того подписчика, который отписался в «неудобный» момент. Но есть неплохой способ избежать последствий такой ситуации.

Примерный код приведен ниже (мое решение в продуктовом коде было несколько более корявым):

    delegate void SomeEventHandler(object sender, EventArgs a);

    class SampleHandlers
    {
        SomeEventHandler someEvent;
        Dictionary<SomeEventHandler, SomeEventHandler> m_actions = new Dictionary<SomeEventHandler, SomeEventHandler>();

        readonly object someEventLock = new object();
        //Подписка/отписка от события
        public event SomeEventHandler SomeEvent
        {
            add
            {
                lock (someEventLock)
                {
                    SomeEventHandler h = new SomeEventHandler((o, a) =>
                    {
                        try { value(o,a); }
                        catch
                        {
                           //тут как-то это обрабатываем. Я или просто пишу в лог, или удаляю 
                           //обработчик (что на самом деле непросто), - в зависимости от типа исключения.
                        }
                    });

                    someEvent += h;
                    m_actions[value] = h;
                }
            }

            remove
            {
                lock (someEventLock)
                {
                    try
                    {
                        SomeEventHandler h = m_actions[value];
                        m_actions.Remove(value);
                        someEvent -= h;
                    }
                    catch
                    { /*тут как-то это обрабатываем, я тут просто пишу в лог*/}
                }
            }
        }

        //Генерация события:
        protected virtual void OnSomeEvent(EventArgs e)
        {
            SomeEventHandler handler;
            lock (someEventLock)
            {
                handler = someEvent;
            }
            if (handler != null)
            {
                handler(this, e);
            }
        }
    }

Как видно, идея просто в том, чтобы вызов каждого клиентского обработчика «на всякий пожарный» обернуть в «свой» блок try-catch. Ну и для нормального «отписывания» пришлось немного нагородить — добавить словарь соответствия между клиентскими обработчиками и нашими. Мне кажется, описанный способ имеет право на существование. Если можно что-то похожее реализовать легче/правильнее, я буду рад ссылкам и комментариям.

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js