Qt 源码分析之moveToThread
这一次,我们来看Qt中关于将一个QObject对象移动至一个线程的函数moveToThread
目录
Qt使用线程的基本方法
首先,我们简单的介绍一下在Qt中使用多线程的几种方法:
- 重写QThread的run函数,将要在多线程执行的任务放到run函数里
- /*mythread.h*/
- #pragma once
- #include <QThread>
- class MyThread : public QThread
- {
- Q_OBJECT
- public:
- explicit MyThread(QObject* parent = nullptr);
- ~MyThread();
- protected:
- void run() override;
- };
- /*mythread.cpp*/
- #include "mythread.h"
- #include <QDebug>
- MyThread::MyThread(QObject* parent)
- : QThread(parent)
- {}
- MyThread::~MyThread()
- {}
- void MyThread::run()
- {
- /*
- 在这个函数里执行耗时操作
- */
- for (auto a = 0; a < 10; a++) {
- qDebug() << u8"线程";
- QThread::sleep(1);
- }
- }
- /*调用函数*/
- auto m_thread = new MyThread();
- // 调用start之后,就会去执行run里内容了
- m_thread->start();
复制代码 但是这种方法,不被Qt官方所推荐,Qt官方所推荐的是将对象移动至线程的方法moveToThread
- 创建一个QThread对象,将对象移动至一个线程中,用信号槽的方式来触发该对象的槽函数,此时槽函数是在线程中执行的
- /*mytask.h*/
- #pragma once
- #include <QObject>
- class MyTask : public QObject
- {
- Q_OBJECT
- public:
- MyTask(QObject *parent = nullptr);
- ~MyTask();
- public slots:
- void slotMyTask();
- };
- /*mytask.cpp*/
- #include "mytask.h"
- #include <QThread>
- #include <QDebug>
- MyTask::MyTask(QObject *parent)
- : QObject(parent)
- {}
- MyTask::~MyTask()
- {}
- void MyTask::slotMyTask()
- {
- /* 在这里执行耗时操作 */
- for (auto a = 0; a < 10; a++) {
- qDebug() << u8"当前线程: " << QThread::currentThread();
- qDebug() << u8"线程";
- QThread::sleep(1);
- }
- }
- /*使用方法*/
- // 1. 创建任务对象以及线程对象
- auto m_task = new MyTask();
- auto* m_thread = new QThread();
- // 2. 将任务对象移动至线程
- m_task->moveToThread(m_thread);
- // 3. 将信号与任务类的槽连接起来
- connect(m_thread, &QThread::started, m_task, &MyTask::slotMyTask);
- // 4. 开启线程
- m_thread->start();
复制代码 此时,我们看到控制台会输出:
Cannot move objects with a parent (无法移动一个有父对象的object)

并且,我们能看到槽函数里打印的线程为主线程。
- 使用Qt的QtConcurrent,缺点之一是没有办法手动退出
- // 1. 创建一个有父对象的任务对象以及线程对象
- auto m_task = new MyTask(this);
- auto* m_thread = new QThread();
- // 2. 将任务对象移动至线程
- m_task->moveToThread(m_thread);
- // 3. 将信号与任务类的槽连接起来
- connect(m_thread, &QThread::started, m_task, &MyTask::slotMyTask);
- // 4. 开启线程
- m_thread->start();
复制代码
- 对要移动的对象当前所属线程的一些判断:
- 如果要移动的对象没有线程依附性,那么可以移动至目标线程
- 如果移动操作所在线程与移动对象所在线程不一致,那么不允许去移动
- // 使用这个,需要在头文件里引入
- #include <QtConcurrent/QtConcurrent>
- // 定义一个任务函数
- int MainWindow::taskTest(int a)
- {
- for (auto i = 1; i < 10; i++) {
- qDebug() << "a: " << a;
- QThread::sleep(1);
- }
- return 0;
- }
- /* 使用方法 */
- // 在函数后面跟上你要设置给函数的参数
- QtConcurrent::run(this, &MainWindow::taskTest, 10);
复制代码- // 当前对象已经在目标线程了
- if (d->threadData.loadRelaxed()->thread.loadAcquire() == targetThread) {
- // object is already in this thread
- return;
- }
- // 不能移动一个有父对象的对象
- if (d->parent != nullptr) {
- qWarning("QObject::moveToThread: Cannot move objects with a parent");
- return;
- }
- // 窗口部件不能移动到一个新的线程,在Qt里GUI操作只能在主线程
- if (d->isWidget) {
- qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread");
- return;
- }
复制代码 一些线程和信号槽使用的心得
到了夹带私活时间,下面是一些多线程使用信号槽的一点小心得总结
- 不能在子线程去更新UI界面,只能在主线程进行更新。
- 可以通过信号槽连接,在子线程通知主线程去更新UI
- 跨线程使用信号槽,建议用QueuedConnection,因为这种连接方式,Qt会把信号丢到事件循环里去,这样槽函数会在接收者所在的线程执行。而DirectConnection这种连接方式,因为是直接回调槽函数,槽会在信号发出的线程进行调用。具体可看上篇关于信号与槽源码分析。
- 但是使用QueuedConnection这种连接方式,信号的参数如果是自己定义的类型,一定要记得使用qRegisterMetaType来进行注册,或者使用Q_DECLARE_METATYPE来进行注册。否则,槽函数将不会触发。
- BlockQueuedConnection这种方法慎用,因为如果信号发送者和接收者在同一个线程,将会导致死锁。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |