EventHandler 订阅者中保证的处理顺序

Guaranteed order of processing in EventHandler subscriber

我有一个异步调用的 EventHandler(使用 BeginInvoke/EndInvoke)。

提供的 EventArgs 包括一个递增的值。

事件正在快速连续地引发多次;事件按顺序引发,以便它们的 EventArgs 值在每次后续调用时递增。

在一个订阅者中,我试图按照事件发生的顺序处理这些事件,我在订阅者中有一个锁,以确保我只能同时处理一个事件,但是,我有一个问题,就是锁是并不总是按照请求的顺序进行处理,导致事件被乱序处理。

我在这里看到:Does lock() guarantee acquired in order requested? 这在某种程度上是意料之中的;也就是说,我不能指望按照请求的顺序获取锁。

该问题的一个答案将我带到此处的排队锁实现:Is there a synchronization class that guarantee FIFO order in C#?

在我走那条路之前,我有三个问题:-

  1. 如果(快速)引发多个事件,是否始终按照事件引发的顺序调用订阅者的处理程序?
  2. 如果我像上面提到的那样实现一个队列锁,我可以依靠按正确顺序调用的 Enter() 方法吗? (即,即使 "event 2" 在 "event 1" 之后触发,"event 2" 是否仍然存在 "event 1" 之前到达我的订阅者中的 queuedLock.Enter() 的风险;和
  3. 考虑到 EventHandler 需要异步(以防止订阅者阻塞线程),这是否只是 possible/reasonable 带有 EventHandler,我是否需要实现某种单独的异步事件队列?

If multiple events are raised (rapidly), will the subscriber's handler always be called in the order the events were raised?

没有。首先,每次调用 BeginInvoke 都会将一个工作项排队到线程池;每个工作项在不同的线程上执行,并且这些线程处于竞争状态。其次,即使您的订阅者以正确的顺序被调用,这些调用仍然处于竞争状态;如果您在订阅者内部获取锁,则未定义授予锁的顺序。

If I implement a queued lock like the one mentioned above, can I rely on the Enter() method being called in the right order? (i.e. is there still a risk that "event 2" will reach the queuedLock.Enter() in my subscriber before "event 1", even if "event 2" fired after "event 1");

没有,原因同上。

Given the EventHandler needs to be async (to prevent subscribers from blocking the thread), is this just not possible/reasonable with an EventHandler and do I need to implement some sort of separate async event queue?

是的。由于您必须按顺序处理事件,因此使用队列优于 multi-threading。产生多个线程只是为了让它们都等待获取一个锁是没有意义的。

使用队列

使用队列时,生产者仅将事件入队而不会阻塞。在消费者端,有一个线程一个一个地出列和处理事件。这是调用每个事件的订阅者的线程。请注意,当队列为空时,消费者线程将被阻塞。

您仍然可以并行处理

例如,如果一个事件属于(比方说)一个客户,则来自同一客户的事件必须按顺序处理,而来自两个不同客户的两个事件可以独立处理。

在这种情况下,您可以将属于不同客户的事件分成多个队列,并且每个队列有一个单独的消费者线程。为此,您必须确保将来自同一客户的事件映射到同一队列。

例如,如果您有 N 个队列,则可以通过计算 hash(Customer) 模 N 将事件映射到队列。

现有 producer-consumer 个队列

.NET 提供了几个开箱即用的专用队列:

你也可以看看: