1

I have an EventHandler that is invoked asynchronously (using BeginInvoke/EndInvoke).

The EventArgs supplied include an incrementing value.

Events are being raised multiple times in quick succession; the events are raised in order such that their EventArgs value is incrementing for each subsequent invocation.

In a subscriber I'm trying to process these events in the order they were raised and I have a lock in the subscriber to ensure I can only process one event concurrently, however, I have an issue that the locks are not always taken in the order they are requested, leading to the events being processed out of order.

I see here: Does lock() guarantee acquired in order requested? that this is somewhat expected; i.e. that I cannot rely on locks being taken in the order they are requested.

One of the answers to that question directed me to a queued lock implementation here: Is there a synchronization class that guarantee FIFO order in C#?

Before I go down that road, I have three questions:-

  1. If multiple events are raised (rapidly), will the subscriber's handler always be called in the order the events were raised?
  2. 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"); and
  3. 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?
Aleks
  • 1,629
  • 14
  • 19

1 Answers1

0

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

No. First, each call to BeginInvoke queues a work item to the thread pool; each work item executes on a different thread, and these threads are in a race. Second, even if your subscribers were invoked in the correct order, these invocations are still in a race; and if you take a lock inside a subscriber, the order in which the locks are granted is not defined.

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");

No, for the same reasons mentioned above.

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?

Yes. Since you have to process events in order, using a queue is preferred over multi-threading. There is no point in spawning multiple threads just to make them all wait to acquire a single lock.

Using a queue

When using a queue, the producer only enqueues an event without blocking. On the consumer side, there is a single thread that dequeues and handles events one by one. This is the thread that invokes the subscribers per each event. Note that the consumer thread will be blocked while the queue is empty.

You can still parallelize the processing

For example, if an event belongs to (let's say) a Customer, and the events from the same Customer must be processed in order, while two events from two different Customers can be processed independently.

In such a case, you can separate events belonging to different Customers into multiple queues, and have a separate consumer thread per queue. For this to work, you must ensure that events from the same Customer are mapped to the same queue.

For example, if you have N queues, you can map an event to a queue by calculating hash(Customer) modulo N.

Existing producer-consumer queues

.NET provides a couple of specialized queues, out of the box:

You may also take a look at:

felix-b
  • 8,178
  • 1
  • 26
  • 36
  • Thank you for a quality answer and for the thread suggestion. I guess the premise was fundamentally flawed at the outset, as soon as you bring async into the equation, you cede the ability to guarantee order.I was looking for a way to use the existing events to signal that I had items in the queue, but realised I'd just moved the same problem to a different spot. For now I'm polling a ConcurrentQueue while I rethink how I'm approaching the problem. – Aleks Jun 06 '19 at 23:30