Diagnosing Silent Signal/Slot Failures in Qt Cross-Thread Connections
A diagnostic guide for troubleshooting silent signal/slot failures in Qt cross-thread communication, covering MOC issues, event loops, and meta-type registration.
09 May 2026, 17:50 UTC

The Problem: The "Silent Failure"
In Qt applications, a common and frustrating failure occurs when a signal emitted from a worker thread fails to trigger a slot in the GUI thread. There is no compiler error, no runtime crash, and often no warning in the console—the slot simply never executes.
This usually happens because the Meta-Object System (the engine behind signals and slots) cannot deliver the event across the thread boundary due to a missing registration, a stalled event loop, or a mismatch in thread affinity.
Diagnostic Matrix
Use this table to match your symptoms to the most likely cause.
| Symptom | Likely Cause | Mechanism |
|---|---|---|
| Slot never runs; no logs. | Missing Q_OBJECT macro |
MOC (Meta-Object Compiler) skips the class; meta-data is missing. |
| Slot runs, but app crashes or data is corrupted. | Incorrect Connection Type | DirectConnection used across threads; slot runs in emitter thread. |
| Slot runs only after the worker thread finishes. | Blocked Event Loop | Receiver thread is busy or exec() was never called. |
| Console warns: "QObject::connect: Cannot queue signal..." | Unregistered Custom Type | Meta-type system doesn't know how to copy the argument for the queue. |
Step-by-Step Diagnostic Workflow
1. Verify Meta-Object Generation
If the Q_OBJECT macro is missing from the class header, the MOC cannot generate the necessary glue code for signals. In Qt 6 with CMake, ensure set(CMAKE_AUTOMOC ON) is present in your CMakeLists.txt.
- Check: Search your build directory for
moc_yourclassname.cpp. If it doesn't exist, the class isn't being processed. - Fix: Add
Q_OBJECTto the private section of your class and rerun the build.
2. Validate Thread Affinity and Connection Type
Qt uses Qt::AutoConnection by default. If the emitter and receiver are in different threads, it defaults to Qt::QueuedConnection, which posts an event to the receiver's event loop. If you explicitly used Qt::DirectConnection, the slot executes in the emitter's thread, bypassing thread safety.
- Check: Use the new functor syntax (pointer-to-member-function) rather than the old
SIGNAL()/SLOT()macros to catch signature mismatches at compile time. - Fix: Use
Qt::QueuedConnectionfor worker-to-GUI communication.
3. Confirm Event Loop Execution
A QueuedConnection requires the receiving thread to be running an event loop. If the receiver is a QThread subclass and you forgot to call exec() in the run() method, or if the GUI thread is blocked by a long-running loop, the slot will never fire.
- Check: Ensure the receiver thread has called
QThread::exec()or is the main thread managed byQCoreApplication::exec(). - Fix: Move heavy processing out of the GUI thread to prevent event loop starvation.
4. Register Custom Argument Types
When using QueuedConnection, Qt must copy the signal arguments into an event object. If you are passing a custom struct or class, the meta-type system must be aware of it.
Example Configuration:
// In the header file
struct WorkerData {
int id;
QString message;
};
Q_DECLARE_METATYPE(WorkerData)
// In the initialization code (before connect())
qRegisterMetaType<WorkerData>("WorkerData");
- Check: Look for runtime warnings regarding "Cannot queue signal".
- Fix: Add
Q_DECLARE_METATYPEin the header and callqRegisterMetaTypein the constructor ormain().
Advanced Diagnostics and Verification
To see exactly how Qt is handling your connections at runtime, enable internal logging. Run your application with the following environment variable:
# Linux/macOS
export QT_LOGGING_RULES="qt.qobject.connection*=true"
# Windows (Cmd)
set QT_LOGGING_RULES=qt.qobject.connection*=true
This will output the connection type (Queued vs Direct) and whether the signal was successfully posted to the event queue.
Verification Test
To verify the fix, create a minimal reproducer:
- Define a
Workerclass with a signal and aReceiverclass with a slot. - Move the
Workerto a newQThreadusingworker->moveToThread(workerThread). - Emit a signal containing a custom registered type.
- Use
qDebug()inside the slot to confirm execution.
Critical Limitations and Risks
- BlockingQueuedConnection: Never use
Qt::BlockingQueuedConnectionif the emitter and receiver are in the same thread; this will cause an immediate deadlock. - Lambda Captures: When connecting to a lambda in a queued connection, avoid capturing local stack variables by reference. Capture by value or pass a
QObjectcontext to theconnect()call to ensure the lambda is disconnected if the receiver is destroyed. - Thread Termination: Avoid
QThread::terminate(). It kills the thread instantly without cleaning up mutexes, often leading to deadlocks in the remaining application. Use a flag orrequestInterruption().
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.