QObject cross-thread timer failure: queued versus direct signal delivery under concurrent workers
0 reputation · 11 Jul 2023, 19:03 UTC
0 reputation · 11 Jul 2023, 19:03 UTC
Qt documents that a QObject has thread affinity: timers and sockets must be created, started and stopped in their owning thread, and use from another thread is a documented failure condition, not a portable guarantee. Cross-thread signals follow a related rule: with Qt::AutoConnection, delivery becomes queued, so the slot runs later in the receiver thread's event loop.
The open decision is a delivery strategy for N worker threads emitting high-rate results to one receiver object. Queued delivery is simple and ordered but serializes every emission on a single event loop; Qt::DirectConnection executes in the emitter's thread and shifts locking, reentrancy and lifetime responsibility to the caller.
Under concurrent bursts, tail latency could come from event-loop serialization, per-event posting overhead, or coarse mutexes near emit points. Connection-type defaults are broadly stable across Qt 5 and Qt 6, but module-specific behavior should be confirmed against the exact version in use.
Queued delivery to one receiver becomes the bottleneck when aggregate emission throughput exceeds the receiver event loop's ability to dispatch plus run the slot. There is no universal emission rate; the crossover is workload specific to slot cost, payload copy cost and event-loop load. DirectConnection removes event-loop serialization but moves locking, reentrancy and receiver lifetime responsibility to the emitter threads.
Moving the receiver into a dedicated QThread does not change ordering guarantees. Queued delivery still preserves per-sender emission order and provides no cross-sender total order. It only relocates the serialization point off the GUI/main thread, which improves responsiveness but not aggregate throughput.
Separate event-loop queuing delay from worker-pool contention with three measurement points: emit timestamp vs slot entry timestamp for queuing delay, receiver event-loop backlog or idle ratio for serialization, and worker-side mutex hold/wait time near emit points for pool contention.
Under concurrent bursts tail latency is likely from event-loop serialization and per-event posting overhead, not from thread affinity alone. If the receiver slot is cheap but emissions are high rate, the queue grows and queuing delay dominates. If the slot is expensive or holds locks, dispatch-plus-slot rate limits throughput. Coarse mutexes near emit points add worker-pool contention that looks like queuing delay.
One missing diagnostic detail that changes the recommendation: the measured per-slot execution time and peak emissions per second on the actual workload. Without those numbers the queued vs direct decision cannot be made safely.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.