Guide
Diagnosing Qt Queued Signal-Slot Connection Failures Across Threads
Step‑by‑step guide to diagnose why a queued Qt signal‑slot connection fails to deliver across thread boundaries.
Published by Tasadduq Burney
10 Aug 2026, 13:55 UTC
3 min31K views0

Recognizable condition
You emit a signal with Qt::QueuedConnection (or let Qt choose queued because objects live in different threads) but the corresponding slot in the receiver object is never invoked. The program shows no error, and the signal‑emit call returns immediately.
Cause and diagnostic indicators
| Possible cause | What to look for |
|---|---|
Missing Q_OBJECT macro in the receiver class | Compiler does not generate meta‑object code; connection fails silently at runtime. |
| Receiver object lives in a thread without an event loop | Queued delivery relies on the target thread’s event loop; if QThread::exec() is not running, events are dropped. |
| Object created in the wrong thread (affinity mismatch) | QObject::thread() returns a thread different from the one you expect for the slot. |
Auto‑connection via QMetaObject::connectSlotsByName mis‑named slot | Slot name does not follow on__ pattern; connection is not made. |
| Blocking or long‑running code in the receiver thread’s event loop | Calls like QThread::msleep or a tight loop prevent the event loop from processing queued events, making the slot appear never called. |
Ordered diagnostic checks
- Verify the receiver class contains the
Q_OBJECTmacro. Look at the class definition; if missing, add it and re‑runqmakeorCMaketo regenerate the moc file. - Confirm the receiver object has an event loop. In the thread that owns the object, ensure you call
QThread::exec()(or start the thread withQThread::startwhich defaults to an event loop). You can check by printingQThread::currentThreadId()inside the slot and verifying it runs. - Check thread affinity: after creating the receiver, call
qDebug() << receiver->thread();and compare with the thread you intend to run the slot in. If they differ, move the object withreceiver->moveToThread(targetThread)before making the connection. - Inspect the connection type used. When you call
connect, explicitly passQt::QueuedConnectionand verify the return value is a validQMetaObject::Connection(not0). - If you rely on
connectSlotsByName, ensure the slot follows the naming conventionon__. Rename either the object or the slot accordingly. - Look for blocking calls in the receiver thread’s event loop. Replace any
QThread::msleeporwhile (!condition) {}with timers or asynchronous checks, or move the blocking work to a worker thread.
Fixes tied to findings
- Add Q_OBJECT: Insert
Q_OBJECTin the private section of the class, rerun the meta‑object compiler, and rebuild. - Start event loop: Ensure the thread runs
exec(); for aQThreadsubclass, do not overriderun()without calling the base implementation, or post work viaQMetaObject::invokeMethod. - Correct thread affinity: After creating the receiver, call
receiver->moveToThread(&targetThread)before establishing the queued connection. - Fix auto‑connection names: Rename the slot to match
on__or connect manually withconnect. - Eliminate blocking code: Replace blocking sleeps with
QTimer::singleShotor move the work to a separateQRunnablemanaged by aQThreadPool.
Escalation criteria
If after performing the checks above the slot still does not invoke:
- Enable Qt debug output by setting the environment variable
QT_DEBUG_PLUGINS=1orQT_LOGGING_RULES=qt.*=trueto see connection warnings. - Try a direct connection (
Qt::DirectConnection) as a test; if the slot runs, the problem is definitely queued‑delivery related (event loop or thread affinity). - Consider using
QMetaObject::invokeMethodwithQt::QueuedConnectionas a lower‑level alternative to verify whether the issue lies in theconnectcall itself. - Check the Qt version; starting with Qt 5.15, queued connection handling for movable types changed. If you are passing custom types by value, ensure they are registered with
qRegisterMetaTypeand have a copy constructor.
When the problem persists, isolate the code into a minimal reproducible example (two threads, one emitter, one receiver) and verify each step in that testbed before reintegrating into the larger application.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.