Qt 信号与槽的 5 种连接方式
Qt 信号与槽提供了 5 种连接方式(Connection Type),用于控制信号发射时槽函数的调用行为。
一、连接方式枚举
cpp
enum ConnectionType {
AutoConnection, // 自动连接(默认)
DirectConnection, // 直接连接
QueuedConnection, // 队列连接
BlockingQueuedConnection, // 阻塞队列连接
UniqueConnection // 唯一连接(标志位,与其他组合使用)
};
二、详细说明
1. AutoConnection(自动连接)
默认方式,根据信号发送者和接收者是否在同一线程自动选择连接类型。
cpp
connect(sender, &Sender::signal, receiver, &Receiver::slot);
// 等价于
connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::AutoConnection);
行为规则:
- 在同一线程:使用
DirectConnection - 在不同线程:使用
QueuedConnection
示例:
cpp
class Worker : public QObject {
Q_OBJECT
public slots:
void doWork() {
qDebug() << "Worker thread:" << QThread::currentThread();
emit result(42);
}
signals:
void result(int value);
};
class Controller : public QObject {
Q_OBJECT
public slots:
void handleResult(int value) {
qDebug() << "Controller thread:" << QThread::currentThread();
qDebug() << "Got result:" << value;
}
};
// 使用场景
Worker *worker = new Worker;
Controller *controller = new Controller;
// 1. 同一线程:直接连接
connect(worker, &Worker::result, controller, &Controller::handleResult);
worker->doWork(); // 槽函数在 worker 线程执行
// 2. 不同线程:队列连接
QThread *thread = new QThread;
worker->moveToThread(thread);
connect(worker, &Worker::result, controller, &Controller::handleResult);
thread->start();
worker->doWork(); // 槽函数在 controller 线程执行(主线程)
输出示例:
plain
// 同一线程
Worker thread: QThread(0x1234)
Controller thread: QThread(0x1234)
// 不同线程
Worker thread: QThread(0x5678)
Controller thread: QThread(0x1234) // 主线程
2. DirectConnection(直接连接)
槽函数在信号发射的线程中立即执行,同步调用。
cpp
connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::DirectConnection);
特点:
- 槽函数在信号发送者的线程中执行
- 同步调用,槽函数返回后才继续执行信号后面的代码
- 类似于直接函数调用
示例:
cpp
class Emitter : public QObject {
Q_OBJECT
public:
void emitSignal() {
qDebug() << "Before emit - thread:" << QThread::currentThread();
emit mySignal();
qDebug() << "After emit - thread:" << QThread::currentThread();
}
signals:
void mySignal();
};
class Receiver : public QObject {
Q_OBJECT
public slots:
void onSignal() {
qDebug() << "In slot - thread:" << QThread::currentThread();
QThread::sleep(2); // 模拟耗时操作
qDebug() << "Slot finished";
}
};
// 使用
Emitter emitter;
Receiver receiver;
connect(&emitter, &Emitter::mySignal, &receiver, &Receiver::onSignal,
Qt::DirectConnection);
emitter.emitSignal();
输出:
plain
Before emit - thread: QThread(0x1234)
In slot - thread: QThread(0x1234)
Slot finished
After emit - thread: QThread(0x1234) // 等待槽函数完成后才输出
注意事项:
- 槽函数执行时间过长会阻塞信号发送者线程
- 如果槽函数和发送者在不同线程,可能导致线程安全问题
- 危险:在不同线程中使用 DirectConnection 可能导致崩溃
3. QueuedConnection(队列连接)
槽函数在接收者所在线程的事件循环中异步执行。
cpp
connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::QueuedConnection);
特点:
- 信号发射后立即返回,不等待槽函数执行
- 槽函数在接收者线程的事件循环中排队执行
- 参数会被复制(需要元类型支持)
示例:
cpp
class Emitter : public QObject {
Q_OBJECT
public:
void emitSignal() {
qDebug() << "Before emit - thread:" << QThread::currentThread();
emit mySignal();
qDebug() << "After emit - thread:" << QThread::currentThread();
}
signals:
void mySignal();
};
class Receiver : public QObject {
Q_OBJECT
public slots:
void onSignal() {
qDebug() << "In slot - thread:" << QThread::currentThread();
QThread::sleep(2);
qDebug() << "Slot finished";
}
};
// 使用
Emitter emitter;
Receiver receiver;
// 将 receiver 移到另一个线程
QThread thread;
receiver.moveToThread(&thread);
thread.start();
connect(&emitter, &Emitter::mySignal, &receiver, &Receiver::onSignal,
Qt::QueuedConnection);
emitter.emitSignal();
qDebug() << "After emit in main thread";
输出:
plain
Before emit - thread: QThread(0x1234) // 主线程
After emit - thread: QThread(0x1234) // 立即返回
After emit in main thread // 立即输出
In slot - thread: QThread(0x5678) // 在 receiver 线程执行
Slot finished // 2 秒后输出
适用场景:
- 跨线程通信(最常用)
- 不需要立即响应的操作
- 避免阻塞信号发送者
4. BlockingQueuedConnection(阻塞队列连接)
与 QueuedConnection 类似,但信号发送者会阻塞等待槽函数执行完成。
cpp
connect(sender, &Sender::signal, receiver, &Receiver::slot,
Qt::BlockingQueuedConnection);
特点:
- 槽函数在接收者线程的事件循环中执行
- 信号发送者线程阻塞直到槽函数返回
- 不能在同一线程中使用(会导致死锁)
示例:
cpp
class Emitter : public QObject {
Q_OBJECT
public:
void emitSignal() {
qDebug() << "Before emit - thread:" << QThread::currentThread();
emit mySignal();
qDebug() << "After emit - thread:" << QThread::currentThread();
}
signals:
void mySignal();
};
class Receiver : public QObject {
Q_OBJECT
public slots:
void onSignal() {
qDebug() << "In slot - thread:" << QThread::currentThread();
QThread::sleep(2);
qDebug() << "Slot finished";
}
};
// 使用
Emitter emitter;
Receiver receiver;
// 将 receiver 移到另一个线程
QThread thread;
receiver.moveToThread(&thread);
thread.start();
// 使用阻塞连接
connect(&emitter, &Emitter::mySignal, &receiver, &Receiver::onSignal,
Qt::BlockingQueuedConnection);
qDebug() << "Main thread before emit";
emitter.emitSignal(); // 这里会阻塞等待槽函数完成
qDebug() << "Main thread after emit";
输出:
plain
Main thread before emit
Before emit - thread: QThread(0x1234) // 主线程
In slot - thread: QThread(0x5678) // receiver 线程
Slot finished // 2 秒后
After emit - thread: QThread(0x1234) // 阻塞完成后才返回
Main thread after emit
适用场景:
- 需要等待跨线程操作完成(如获取返回值)
- 需要同步结果的跨线程调用
注意: 同一线程中使用会导致死锁!
cpp
// ❌ 错误:同一线程使用 BlockingQueuedConnection 会死锁
Emitter emitter;
Receiver receiver;
connect(&emitter, &Emitter::mySignal, &receiver, &Receiver::onSignal,
Qt::BlockingQueuedConnection);
emitter.emitSignal(); // 死锁!信号发送者和接收者在同一线程
5. UniqueConnection(唯一连接)
这是一个标志位,与其他类型组合使用,确保同一个信号-槽对只连接一次。
cpp
// 避免重复连接
connect(sender, &Sender::signal, receiver, &Receiver::slot,
Qt::UniqueConnection); // 自动与 AutoConnection 组合
// 组合使用
connect(sender, &Sender::signal, receiver, &Receiver::slot,
Qt::QueuedConnection | Qt::UniqueConnection);
特点:
- 防止同一个信号连接到同一个对象的同一个槽多次
- 如果连接已存在,connect 返回 false
- 通常与其他连接类型组合使用
示例:
cpp
class Emitter : public QObject {
Q_OBJECT
signals:
void mySignal();
};
class Receiver : public QObject {
Q_OBJECT
public slots:
void onSignal() {
static int count = 0;
qDebug() << "Slot called:" << ++count;
}
};
// 使用
Emitter emitter;
Receiver receiver;
// 第一次连接
bool success = connect(&emitter, &Emitter::mySignal,
&receiver, &Receiver::onSignal,
Qt::AutoConnection | Qt::UniqueConnection);
qDebug() << "First connect:" << success; // true
// 第二次连接相同信号槽
success = connect(&emitter, &Emitter::mySignal,
&receiver, &Receiver::onSignal,
Qt::AutoConnection | Qt::UniqueConnection);
qDebug() << "Second connect:" << success; // false
// 发射信号
emit emitter.mySignal(); // 槽函数只调用一次
输出:
plain
First connect: true
Second connect: false
Slot called: 1
三、连接方式对比表
| 连接类型 | 执行线程 | 是否阻塞 | 跨线程安全 | 参数传递 | 使用场景 |
|---|---|---|---|---|---|
| AutoConnection | 自动选择 | 看情况 | ✅ | 自动处理 | 默认选择 |
| DirectConnection | 发送者线程 | ✅ 阻塞 | ❌ 危险 | 引用传递 | 同线程高性能调用 |
| QueuedConnection | 接收者线程 | ❌ 非阻塞 | ✅ | 值传递(需复制) | 跨线程通信 |
| BlockingQueuedConnection | 接收者线程 | ✅ 阻塞 | ✅ | 值传递(需复制) | 等待跨线程结果 |
| UniqueConnection | 标志位 | N/A | N/A | N/A | 防止重复连接 |
四、实际应用示例
完整的跨线程通信示例
cpp
// 工作线程类
class Worker : public QObject
{
Q_OBJECT
public:
Worker() {
qDebug() << "Worker created in thread:" << QThread::currentThread();
}
public slots:
void startWork() {
qDebug() << "Worker working in thread:" << QThread::currentThread();
for (int i = 1; i <= 5; ++i) {
emit progress(i * 20);
QThread::sleep(1);
}
emit finished("Work completed successfully!");
}
signals:
void progress(int percent);
void finished(const QString& result);
};
// 控制器类(在主线程)
class Controller : public QObject
{
Q_OBJECT
public:
Controller() {
qDebug() << "Controller created in thread:" << QThread::currentThread();
// 创建工作对象和线程
m_worker = new Worker;
m_thread = new QThread(this);
m_worker->moveToThread(m_thread);
// 跨线程连接(自动使用 QueuedConnection)
connect(m_thread, &QThread::started,
m_worker, &Worker::startWork);
connect(m_worker, &Worker::progress,
this, &Controller::onProgress);
// 等待工作完成(使用 BlockingQueuedConnection)
connect(m_worker, &Worker::finished,
this, &Controller::onFinished);
// 工作完成后的清理(使用 DirectConnection 同线程)
connect(this, &Controller::cleanup,
this, &Controller::doCleanup,
Qt::DirectConnection);
}
void start() {
qDebug() << "Starting worker thread...";
m_thread->start();
}
public slots:
void onProgress(int percent) {
qDebug() << "Progress in thread:" << QThread::currentThread();
qDebug() << "Progress:" << percent << "%";
if (percent == 100) {
// 演示 BlockingQueuedConnection
QString result;
QMetaObject::invokeMethod(m_worker, "getResult",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QString, result));
qDebug() << "Got result with blocking:" << result;
}
}
void onFinished(const QString& result) {
qDebug() << "Finished in thread:" << QThread::currentThread();
qDebug() << "Result:" << result;
// 清理工作
emit cleanup();
}
void doCleanup() {
qDebug() << "Cleanup in thread:" << QThread::currentThread();
m_thread->quit();
m_thread->wait();
delete m_worker;
}
signals:
void cleanup();
private:
Worker *m_worker;
QThread *m_thread;
};
// 使用
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
qDebug() << "Main thread:" << QThread::currentThread();
Controller controller;
controller.start();
return app.exec();
}
五、选择指南
cpp
// 1. 同线程,需要高性能,不关心阻塞
connect(a, &A::signal, b, &B::slot, Qt::DirectConnection);
// 2. 跨线程通信(最常用)
connect(a, &A::signal, b, &B::slot, Qt::QueuedConnection);
// 3. 需要等待跨线程结果
connect(a, &A::signal, b, &B::slot, Qt::BlockingQueuedConnection);
// 4. 防止重复连接
connect(a, &A::signal, b, &B::slot, Qt::UniqueConnection);
// 5. 默认情况,让 Qt 自动选择
connect(a, &A::signal, b, &B::slot); // AutoConnection
六、常见错误与注意事项
cpp
// ❌ 错误1:跨线程使用 DirectConnection
// 可能导致崩溃或未定义行为
QThread thread;
MyObject obj;
obj.moveToThread(&thread);
connect(&obj, &MyObject::signal, &receiver, &Receiver::slot,
Qt::DirectConnection); // 危险!
// ❌ 错误2:同一线程使用 BlockingQueuedConnection
connect(&obj, &MyObject::signal, &receiver, &Receiver::slot,
Qt::BlockingQueuedConnection); // 死锁!
// ❌ 错误3:未注册的自定义类型用于队列连接
struct MyData { int value; }; // 未注册
connect(this, &MyClass::sendData, receiver, &Receiver::processData);
// 运行时会报错:QObject::connect: Cannot queue arguments of type 'MyData'
// ✅ 正确:注册自定义类型
qRegisterMetaType<MyData>("MyData");
// ✅ 正确:多线程通信模式
// 发送者线程 → 队列连接 → 接收者线程
七、总结
| 连接方式 | 记忆口诀 | 核心特征 |
|---|---|---|
| AutoConnection | "自动选择" | 默认方式,智能选择 |
| DirectConnection | "同步调用" | 立即执行,阻塞发送者 |
| QueuedConnection | "异步排队" | 延迟执行,不阻塞发送者 |
| BlockingQueuedConnection | "同步跨线程" | 跨线程阻塞等待结果 |
| UniqueConnection | "唯一连接" | 防止重复连接 |
| 场景 | 选择 |
|---|---|
| 跨线程同步阻塞等待结果 | ✅ BlockingQueuedConnection |
| 跨线程异步执行不阻塞 | ✅ QueuedConnection |
| 同线程异步执行不阻塞 | ✅ QueuedConnection |
| 同线程阻塞等待结果 | ✅ DirectConnection |
| 跨线程阻塞等待结果 | ✅ BlockingQueuedConnection |
| 防止重复连接 | ✅ UniqueConnection |
最佳实践:
- 默认使用 AutoConnection,让 Qt 自动选择
- 跨线程通信使用 QueuedConnection
- 需要返回值使用 BlockingQueuedConnection
- 同线程高性能调用可使用 DirectConnection
- 防止重复连接添加 UniqueConnection 标志
- 自定义类型用于队列连接时务必注册元类型