Гарантированный порядок обработки в подписчике EventHandler - PullRequest
1 голос
/ 06 июня 2019

У меня есть EventHandler, который вызывается асинхронно (используя BeginInvoke / EndInvoke).

В поставляемых EventArgs включено увеличивающееся значение.

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

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

Я вижу здесь: Гарантирует ли блокировка (), полученная в запрошенном порядке? , что это несколько ожидаемо; то есть я не могу полагаться на то, что блокировки были взяты в том порядке, в котором они запрашиваются.

Один из ответов на этот вопрос направил меня к реализации блокировки в очереди: Существует ли класс синхронизации, который гарантирует порядок FIFO в C #?

Прежде чем идти по этому пути, у меня есть три вопроса: -

  1. Если несколько событий возникают (быстро), будет ли обработчик подписчика всегда вызываться в том порядке, в котором были вызваны события?
  2. Если я реализую блокировку в очереди, подобную упомянутой выше, могу ли я рассчитывать на метод Enter(), вызываемый в правильном порядке? (т.е. все еще существует риск того, что «событие 2» достигнет queuedLock.Enter() в моем подписчике до «события 1», даже если «событие 2» сработало после «события 1»); и
  3. Учитывая, что EventHandler должен быть асинхронным (чтобы подписчики не блокировали поток), это просто невозможно / разумно с EventHandler, и мне нужно реализовать какую-то отдельную очередь асинхронных событий?

1 Ответ

0 голосов
/ 06 июня 2019

Если многократные события возникают (быстро), будет ли обработчик подписчика всегда вызываться в том порядке, в котором были вызваны события?

Нет.Во-первых, каждый вызов BeginInvoke ставит рабочий элемент в очередь потоков;каждый рабочий элемент выполняется в отдельном потоке, и эти потоки участвуют в гонке.Во-вторых, даже если ваши подписчики были вызваны в правильном порядке, эти вызовы все еще в гонке;и если вы берете блокировку внутри подписчика, порядок, в котором предоставляются блокировки, не определен.

Если я реализую блокировку в очереди, подобную упомянутой выше, могу ли я положиться на Enter () метод вызывается в правильном порядке?(т.е. все еще существует риск того, что «событие 2» достигнет queuedLock.Enter () в моем подписчике до «события 1», даже если «событие 2» сработало после «события 1»);

Нет, по тем же причинам, указанным выше.

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

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

Использование очереди

При использовании очереди производитель только ставит в очередь событие без блокировки.На стороне потребителя есть один поток, который удаляет и обрабатывает события один за другим.Это поток, который вызывает подписчиков для каждого события.Обратите внимание, что потребительский поток будет заблокирован, пока очередь пуста.

Вы все равно можете распараллелить обработку

Например, если событие принадлежит (скажем, клиенту), а событияОт одного и того же Заказчика необходимо обрабатывать по порядку, тогда как два события от двух разных Заказчиков могут обрабатываться независимо

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

Например, если у вас есть N очередей, вы можете отобразить событие в очередь, вычислив хэш (Customer) по модулю N.

Существующие очереди производителя-потребителя

.NET предоставляет несколько специализированных очередей, из коробки:

Вы также можете взглянуть на:

  • TPL DataFlow , который предоставляет более производительные альтернативы.
  • LMAX Disruptor , если вам нужен сверхэффективный бэкэнд.
...