Qt 中每个 QObject 及其子类的对象都有自己的线程上下文环境,即对象所属的线程,对象属于创建它的函数执行时所在的线程,例如在 Ui 线程中的函数里创建了对象 ops,则 ops 属于 Ui 线程。使用 QObject::thread()
获取对象所属线程,可以使用 QObject::moveToThread(otherThread)
移动一个对象到另一个线程。由于跨线程调用函数有可能会造成程序崩溃,所以有比较了解代码在执行时它所处的线程。
下面以示例演示不同情况下代码执行时所处的线程:
- Widget 所属线程
- 线程对象所属线程
- Lambda 的方式处理信号槽
- Qt 5 函数指针的方式处理信号槽
- 传统 signal slot 的方式处理信号槽
注意: 下面的注释都是基于示例代码的,为了不让描述的太过繁琐,便于帮助理解,有些描述理论上可能是不精确的,需要大家自行更进一步的分析。
涉及到的文件有:
- Widget.h
- Widget.cpp
- Thread.h
- Thread.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #ifndef WIDGET_H #define WIDGET_H
#include <QWidget>
class Thread;
class Widget : public QWidget { Q_OBJECT
public: explicit Widget(QWidget *parent = nullptr); ~Widget();
Q_INVOKABLE void directCall(); Q_INVOKABLE void invokeCall(); Q_INVOKABLE void slotCall();
private: Thread *thread; };
#endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include "Widget.h" #include "Thread.h" #include <QDebug> #include <QMetaObject>
Widget::Widget(QWidget *parent) : QWidget(parent) { qDebug() << "1-Widget::Widget(): " << QThread::currentThread(); thread = new Thread(); qDebug() << "3-Widget.thread: " << thread->thread(); this->directCall();
connect(thread, &Thread::beat, [this]() { qDebug() << "Lambda-1: " << QThread::currentThread(); this->directCall(); QMetaObject::invokeMethod(this, "invokeCall"); });
connect(thread, &Thread::beat, this, [this]() { qDebug() << "Lambda-2: " << QThread::currentThread(); this->directCall(); QMetaObject::invokeMethod(this, "invokeCall"); });
connect(thread, &Thread::beat, this, &Widget::slotCall);
connect(thread, SIGNAL(beat()), this, SLOT(slotCall()));
thread->start(); }
Widget::~Widget() {}
void Widget::directCall() { qDebug() << "directCall: " << QThread::currentThread(); }
void Widget::invokeCall() { qDebug() << "invokeCall: " << QThread::currentThread(); }
void Widget::slotCall() { qDebug() << "slotCall: " << QThread::currentThread(); }
|
类 Thread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #ifndef THREAD_H #define THREAD_H
#include <QThread>
class Thread : public QThread { Q_OBJECT public: Thread();
protected: void run() override;
signals: void beat(); };
#endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include "Thread.h" #include <QDateTime> #include <QDebug>
Thread::Thread() { qDebug() << "2-Thread::Thread(): " << QThread::currentThread(); }
void Thread::run() { qDebug() << "4-Thread.run(): " << QThread::currentThread();
while (true) { emit beat(); QThread::msleep(2000); } }
|
运行
运行程序,输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 1-Widget:: Widget(): QThread(0x7fdde040e5e0) 2-Thread:: Thread(): QThread(0x7fdde040e5e0) 3-Widget.thread : QThread(0x7fdde040e5e0) directCall : QThread(0x7fdde040e5e0) 4-Thread.run() : Thread(0x7fdde040f070) Lambda-1 : Thread(0x7fdde040f070) directCall : Thread(0x7fdde040f070) invokeCall : QThread(0x7fdde040e5e0) Lambda-2 : QThread(0x7fdde040e5e0) directCall : QThread(0x7fdde040e5e0) invokeCall : QThread(0x7fdde040e5e0) slotCall : QThread(0x7fdde040e5e0) slotCall : QThread(0x7fdde040e5e0)
|
根据输出结果,参考代码,结论如下 (这里的 connection type 使用 Qt::AutoConnection):
Widget 的构造函数属于 Ui 线程,在此构造函数里面创建了 Thread 的对象 thread,所以 thread 属于 Ui 线程
线程对象 thread 的 run()
函数属于 Thread 自己的线程,而不是 Ui 线程
直接调用的函数属于调用它时代码所在的线程,不管它是谁的函数
使用 invokeMethod()
调用的函数属于它的对象所在的线程,不管它在哪个线程中被调用
使用 Lambda 的方式处理信号槽,如果 connect 的第三个参数是一个指针对象,即线程的 context,则 Lambda 函数执行时的线程上下文为 context 所属的线程,否则属于线程对象 thread,connect 的函数签名如下:
1 2
| QObject::connect(const QObject *sender, PointerToMemberFunction signal, Functor functor) QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)
|
传统 signal slot 调用的函数属于 receiver 所属的线程