Message Ordering for OnMessageReceived
MQTT 5.0 section 4.6 (Message Ordering) requires that QoS 1 and QoS 2 application messages be delivered to a client in the order they were sent for a given Topic Filter.
HiveMQtt preserves that guarantee at the OnMessageReceived event boundary for QoS 1 and QoS 2 messages.
What is guaranteed
For QoS 1 and QoS 2 publishes:
- Handlers are started in FIFO order on a single per-client dispatch queue.
- Within one message, global
OnMessageReceivedhandlers run first (in registration order), then per-subscription handlers (in subscription order, respectingOverlappingSubscriptionBehavior). - Handlers run on a dedicated message dispatch thread (not arbitrary thread-pool workers).
The guarantee is invocation (start) order, not completion order.
Async handlers
OnMessageReceived uses the standard .NET EventHandler<T> signature, which returns void. If you register an async lambda, it becomes async void:
client.OnMessageReceived += async (sender, args) =>
{
await SaveToDatabaseAsync(args); // ordering does NOT extend past the first await
};
Only synchronous work before the first await and handler start order are ordered. Work that continues after await may overlap with later messages.
Recommendation: keep handlers thin and synchronous — parse the payload, enqueue to your own worker/channel, and return. Perform I/O and heavy logic on application-owned consumers.
QoS 0
QoS 0 has no ordering requirement in the MQTT specification. HiveMQtt may invoke QoS 0 OnMessageReceived handlers concurrently via the thread pool.
Disconnect behavior
When the client disconnects, the library quiesces the dispatch queue (similar to Eclipse Paho):
- The in-flight handler (if any) is allowed to finish.
- Pending handlers that have not started are dropped.
- New messages are not dispatched after quiesce begins.
After a successful reconnect, dispatch resumes for new messages.
Manual acknowledgement
If you use manual ack, unacknowledged messages consume Receive Maximum slots until you call AckAsync. Ordered dispatch with slow handlers can cause the broker to stop sending new messages sooner — that is expected back-pressure. See the manual ack guide for details.
See also
- Subscribing — registering
OnMessageReceivedhandlers - Manual Acknowledgement — ack timing and Receive Maximum
- Lifecycle Events Reference —
OnMessageReceivedevent args