Identifying Which Qt Worker Sent a Signal: Object Identity vs Thread ID
A common question when building multi-threaded Qt applications with worker objects: “A slot is connected to signals from several workers — how do I tell which one just fired?”
The instinctive first answer is often “compare thread IDs.” That instinct is wrong, and it’s worth being precise about why — because the right answer (comparing object identity) is both simpler and more idiomatic Qt.
Why Thread ID Is the Wrong Tool
QThread::currentThreadId() returns the native thread handle for whatever thread is currently executing. QObject::thread() returns the QThread* an object has affinity with — the thread its event loop and queued slots run on.
These solve a different problem than “which object sent this.” A thread ID doesn’t identify a QObject — several workers could share a thread, or a slot could be invoked via a direct connection from a thread that isn't any worker's affinity thread at all. Building a thread ID → object lookup table just to answer "who called this slot" is solving an identity problem with a concurrency primitive. It's the wrong layer.
The Valid Approach: Compare Sender Identity
If each worker is a distinct QObject*, and the goal is "which known worker triggered this," the direct and idiomatic tool is QObject::sender(), compared against the pointers you already hold:
connect(worker1, &Worker::someSignal, this, &Controller::handleSignal);
connect(worker2, &Worker::someSignal, this, &Controller::handleSignal);void Controller::handleSignal()
{
QObject* obj = sender(); if (obj == worker1)
{
// signal came from worker1
}
else if (obj == worker2)
{
// signal came from worker2
}
}This is a legitimate, working pattern — sender() returns the QObject* that emitted the signal currently being handled, and pointer comparison against known workers correctly identifies the source. If you subsequently need the thread, sender()->thread() gives it to you. The concept is valid.
Get Anuj Gupta’s stories in your inbox
Join Medium for free to get updates from this writer.
But it comes with three caveats worth knowing before you rely on it.
Caveat 1 — sender() is only meaningful inside a slot invoked by a signal
If handleSignal() is ever called directly (not through the signal/slot mechanism — e.g. controller.handleSignal() from elsewhere), sender() returns nullptr. Any code depending on the if (obj == worker1) branches silently falls through. This is easy to miss if the same slot doubles as an ordinary member function.
Caveat 2 — Not thread-safe for direct cross-thread connections
The Qt documentation is explicit that sender() should not be relied on when a Qt::DirectConnection crosses threads — the sender's identity can be read from a different thread than the one setting it, which is a race. This is generally fine for the common case (Qt::AutoConnection resolving to Qt::QueuedConnection between threads, which is by far the typical worker-thread setup), but it's a sharp edge if anyone later adds a direct connection between threads for performance reasons.
Caveat 3 — Breaks encapsulation
The Qt docs themselves flag this: sender() couples the slot to knowledge of who might be calling it, which cuts against the whole point of signals/slots being decoupled. It works, but it's a smell if overused — especially as the number of connected workers grows and the if/else if chain grows with it.
A Cleaner Alternative: Pass Identity Explicitly
Rather than reconstructing “who sent this” after the fact via sender(), it's often cleaner to have the signal carry its own identity as a parameter, or to bind identity at connection time with a lambda:
// Option A: the signal itself carries an identifier
connect(worker1, &Worker::someSignal, this,
[this](int result) { handleSignal(worker1, result); });connect(worker2, &Worker::someSignal, this,
[this](int result) { handleSignal(worker2, result); });void Controller::handleSignal(Worker* source, int result)
{
if (source == worker1) { /* ... */ }
}This avoids all three caveats above: it works regardless of how the slot is invoked, it has no cross-thread timing dependency since the pointer is captured at connect time, and the slot’s signature makes its dependency on “who calls it” explicit rather than implicit.
Bottom Line
Question Right tool “Which thread is executing right now?” QThread::currentThreadId() "What thread does this object belong to?" object->thread() "Which known object just emitted this signal?" sender() compared to known pointers, or better, a lambda capture "I only have a thread ID, give me the QThread*" Neither — you need an existing object/mapping; there's no built-in reverse lookup
The original idea — compare the sender’s object identity rather than chase thread IDs — is sound and more idiomatic than the thread-ID approach it replaced. Treat sender() as fine for a quick fix in single-purpose slots with queued connections, and reach for explicit lambda-captured identity once the caveats above start to matter (mixed connection types, slots called from multiple paths, or a growing number of workers).






