序言
多线程一般用在:
在QT的 UI 编程中, 如果有一个函数消耗的时间特别长, 并且运行于主线程, 那么界面的响应会很不灵敏.。比如说 如果该函数执行时间很长, 为了通知任务的进度, 一般会使用进度条. 但有时候无法准确的使用进度条, 比如在数据库操作中, 为了提高读写数据库的性能, 通常会采用事务操作, 多个读写数据库的操作合并成了一个事务, 此时如何设置进度条的进度值。
Qt 多线程常见得三种方法:
1. 继承自QThread.
2. moveToThread.
3. QtConcurrent::run().
方法
继承QThread
说明
- 自定义继承QThread类,需要重写run函数,去依据需要实现自己的方法。
- 调用start() 启动。
代码
MyThread
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QObject>
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject* parent = nullptr);
~MyThread();
signals:
void SigMyThread(int para);
public slots:
void onMyThread(int para);
protected:
void run() override;
};
#endif // MYTHREAD_H
#include "mythread.h"
#include <QDebug>
MyThread::MyThread(QObject* parent)
:QThread (parent)
{
}
MyThread::~MyThread()
{
}
void MyThread::onMyThread(int para)
{
qDebug()<< __FUNCTION__ <<"current thread ID:"<<QThread::currentThreadId();
int count = 888;
for(int i = 0;i!=1000000;++i)
{
++count;
}
}
void MyThread::run()
{
qDebug()<< __FUNCTION__ <<"current thread ID:"<<QThread::currentThreadId();
int count = 0;
for(int i = 0;i!=1000000;++i)
{
++count;
}
emit SigMyThread(count);
exec();
}
Controller
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include <QObject>
#include <QThread>
#include "mythread.h"
class Controller : public QObject
{
Q_OBJECT
public:
explicit Controller(QObject *parent = nullptr);
~Controller();
signals:
void sigOperate( int para);
public slots:
void onHandleResult( int result);
private:
QThread m_WorkerThread;
MyThread* m_pMyThread = nullptr;
};
#endif // CONTROLLER_H
#include "controller.h"
#include <QDebug>
#include "worker.h"
Controller::Controller(QObject *parent)
: QObject(parent)
{
#if 0
Worker *worker = new Worker;
worker->moveToThread(&m_pWorkerThread); //调用moveToThread将该任务交给workThread
connect(this, SIGNAL(sigOperate(int)), worker, SLOT(onDoWork(int))); //operate信号发射后启动线程工作
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); //该线程结束时销毁
connect(worker, SIGNAL(sigResultReady(int)), this, SLOT(onHandleResult(int))); //线程结束后发送信号,对结果进行处理
m_pWorkerThread.start(); //启动线程
qDebug()<<__FUNCTION__<<"current thread ID:"<<QThread::currentThreadId();
emit sigOperate(0);
#endif
m_pMyThread = new MyThread;
connect(m_pMyThread,&MyThread::SigMyThread,this,&Controller::onHandleResult);
connect(m_pMyThread, &QThread::finished, this, &QObject::deleteLater); //该线程结束时销毁
connect(this,&Controller::sigOperate,m_pMyThread,&MyThread::onMyThread);
m_pMyThread->start();
m_pMyThread->sleep(3);
qDebug()<<__FUNCTION__<<"current thread ID:"<<QThread::currentThreadId();
// QThread::sleep(5); 延迟5s 所有线程阻塞
emit sigOperate(999);
}
Controller::~Controller()
{
#if 0
m_pWorkerThread.quit();
m_pWorkerThread.wait();
#endif
m_pMyThread->quit();
m_pMyThread->wait();
}
void Controller::onHandleResult( int result)
{
qDebug() << __FUNCTION__ <<"current ID :" <<QThread::currentThreadId();
qDebug() << "the result is " <<result;
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
#include "controller.h"
#include <QThread>
#include <QDebug>
#include <QTime>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Controller controller;
QTime dieTime = QTime::currentTime().addMSecs(1);
while( QTime::currentTime() < dieTime)
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
qDebug() << "main CurrentID :" << QThread::currentThreadId();
return a.exec();
}
结果:
moveToThread
说明
- 定义一个继承于QObject的worker类,在worker类中定义一个槽函数,这个函数中定义线程需要做的工作.
- 在要使用线程的controller类中,新建一个QThread的对象和woker类对象,使用moveToThread()方法将worker对象的事件循环全部交由QThread对象处理.
- 建立相关的信号函数和槽函数进行连接,然后发出信号触发QThread的槽函数,使其执行工作.
代码
worker
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
~Worker();
signals:
void sigResultReady(int result);
public slots:
void onDoWork(int para);
};
#endif // WORKER_H
#include "worker.h"
#include <QDebug>
#include <QThread>
Worker::Worker(QObject *parent)
: QObject(parent)
{
}
Worker::~Worker()
{
}
void Worker::onDoWork(int para)
{
qDebug()<<__FUNCTION__<< "current ID :" << QThread::currentThreadId();
for(int i = 0; i != 1000000; ++i)
{
++para;
}
emit sigResultReady(para);
}
Controller
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include <QObject>
#include <QThread>
#include "mythread.h"
class Controller : public QObject
{
Q_OBJECT
public:
explicit Controller(QObject *parent = nullptr);
~Controller();
signals:
void sigOperate( int para);
public slots:
void onHandleResult( int result);
private:
QThread m_WorkerThread;
MyThread* m_pMyThread = nullptr;
};
#endif // CONTROLLER_H
#include "controller.h"
#include <QDebug>
#include "worker.h"
Controller::Controller(QObject *parent)
: QObject(parent)
{
#if 1
Worker *worker = new Worker;
worker->moveToThread(&m_WorkerThread); //调用moveToThread将该任务交给workThread
connect(this, SIGNAL(sigOperate(int)), worker, SLOT(onDoWork(int))); //operate信号发射后启动线程工作
connect(&m_WorkerThread, &QThread::finished, worker, &QObject::deleteLater); //该线程结束时销毁
connect(worker, SIGNAL(sigResultReady(int)), this, SLOT(onHandleResult(int))); //线程结束后发送信号,对结果进行处理
m_WorkerThread.start(); //启动线程
qDebug()<<__FUNCTION__<<"current thread ID:"<<QThread::currentThreadId();
emit sigOperate(0);
#endif
#if 0
m_pMyThread = new MyThread;
connect(m_pMyThread,&MyThread::SigMyThread,this,&Controller::onHandleResult);
connect(m_pMyThread, &QThread::finished, this, &QObject::deleteLater); //该线程结束时销毁
connect(this,&Controller::sigOperate,m_pMyThread,&MyThread::onMyThread);
m_pMyThread->start();
m_pMyThread->sleep(3);
qDebug()<<__FUNCTION__<<"current thread ID:"<<QThread::currentThreadId();
// QThread::sleep(5); 延迟5s 所有线程阻塞
emit sigOperate(999);
#endif
}
Controller::~Controller()
{
#if 0
m_pWorkerThread.quit();
m_pWorkerThread.wait();
#endif
m_pMyThread->quit();
m_pMyThread->wait();
}
void Controller::onHandleResult( int result)
{
qDebug() << __FUNCTION__ <<"current ID :" <<QThread::currentThreadId();
qDebug() << "the result is " <<result;
}
main
#include "mainwindow.h"
#include <QApplication>
#include "controller.h"
#include <QThread>
#include <QDebug>
#include <QTime>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
#if 1
Controller controller;
qDebug() << "main CurrentID :" << QThread::currentThreadId();
#endif
return a.exec();
}
结果:
QtConcurrent::run()
说明
需要了解如下类 :
- QtConcurrent
- QFutureWatcher
- QFuture
QtConcurrent 是一个名字空间, 其内包含了众多的高级 API, 方便用户编写多线程程序.
QFutureWatcher 可以用于监视线程的完成情况, 并获取线程的返回值.
QtConcurrent 线程函数与 QFutureWatcher 之间的中间者是 QFuture.
代码
mainwindow
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QFutureWatcher>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void busyJob();
private:
int doBusyJob();
void busyJobFinished();
private:
Ui::MainWindow *ui;
QFutureWatcher<int>* m_pWatch = nullptr;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtConcurrent>
#include <QFuture>
#include <QDebug>
#include <QTimer>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_pWatch = new QFutureWatcher<int>;
connect(m_pWatch, &QFutureWatcher<int>::finished,
this, &MainWindow::busyJobFinished);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::busyJob()
{
qDebug()<<__FUNCTION__<<"current thread ID:"<<QThread::currentThreadId();
auto future = QtConcurrent::run(this, &MainWindow::doBusyJob);
m_pWatch->setFuture(future);
}
int MainWindow::doBusyJob()
{
qDebug()<<__FUNCTION__<<"current thread ID:"<<QThread::currentThreadId();
return 1;
}
void MainWindow::busyJobFinished()
{
qDebug()<<__FUNCTION__<<"current thread ID:"<<QThread::currentThreadId();
qDebug()<<__FUNCTION__<< "the returned value is: "
<< m_pWatch->result();
}
main
#include "mainwindow.h"
#include <QApplication>
#include "controller.h"
#include <QThread>
#include <QDebug>
#include <QTime>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
#if 1
MainWindow w;
qDebug()<<__FUNCTION__<<"current thread ID:"<<QThread::currentThreadId();
w.busyJob();
QTime dieTime = QTime::currentTime().addMSecs(1);
while( QTime::currentTime() < dieTime)
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
qDebug()<<__FUNCTION__<<"current thread ID:"<<QThread::currentThreadId();
w.show();
#endif
return a.exec();
}
结果:
方法比较
moveToThread方法,是把我们需要的工作全部封装在一个类中,将每个任务定义为一个的槽函数,再建立触发这些槽的信号,然后把信号和槽连接起来,最后将这个类调用moveToThread方法交给一个QThread对象,再调用QThread的start()函数使其全权处理事件循环。于是,任何时候我们需要让线程执行某个任务,只需要发出对应的信号就可以。其优点是我们可以在一个worker类中定义很多个需要做的工作,然后发出触发的信号线程就可以执行。相比于子类化的QThread只能执行run()函数中的任务,moveToThread的方法中一个线程可以做很多不同的工作(只要发出任务的对应的信号即可)。
子类化QThread的方法,就是重写了QThread中的run()函数,在run()函数中定义了需要的工作。这样的结果是,我们自定义的子线程调用start()函数后,便开始执行run()函数。如果在自定义的线程类中定义相关槽函数,那么这些槽函数不会由子类化的QThread自身事件循环所执行,而是由该子线程的拥有者所在线程(一般都是主线程)来执行。如果你不明白的话,请看,第二个例子中,子类化的线程的槽函数中输出当前线程的ID,而这个ID居然是主线程的ID!!事实的确是如此,子类化的QThread只能执行run()函数中的任务直到run()函数退出,而它的槽函数根本不会被自己的线程执行。
扩展
1: 继承自QThread
2: 把自己moveToThread
/*ChildThread.h*/
class ChildThread: public QThread{
Q_OBJECT
public:
explicit ChildThread(QObject* parent = nullptr);
public slots:
void stopTest();
protected:
void run() override;
private:
bool m_quit = false;
};
/*ChildThread.cpp*/
ChildThread::ChildThread(QObject *parent)
:QThread(parent)
,m_quit(false)
{
qDebug() << __FUNCTION__ << QThread::currentThreadId();
moveToThread(this);
start();
}
void ChildThread::stopTest()
{
qDebug() << __FUNCTION__ << QThread::currentThreadId();
m_quit = true;
}
void ChildThread::run()
{
// 方案一
// QEventLoop eventloop;
// while(!m_quit){
// qDebug() << __FUNCTION__ << QThread::currentThreadId();
// QTimer::singleShot(300, &eventloop, &QEventLoop::quit);
// eventloop.exec();
// }
// 方案二
while(!m_quit){
qDebug() << __FUNCTION__ << QThread::currentThreadId();
QThread::msleep(400);
}
}
/*MainWindow.cpp*/
ChildThread test 作为子成员, 通过如下信号槽绑定
connect(ui->pushButton_4, &QPushButton::clicked, &test, &ChildThread::stopTest, Qt::QueuedConnection);
发现一: 如果采用方案二, MainWindow 点击按钮 , ChildThread 是收不到信号的, 因为ChildThread线程没有事件循环, 此时事件是收不到的
发现二: 如果采用方案一, MainWindow 点击按钮 , ChildThread 是能收到信号的, 因为ChildThread线程有事件循环, 此时事件是能收到的 ,并且此时对应槽函数 是和run 处于同一个线程,解决了之前继承QThread只有run 函数是处于另一个线程, 同时一旦退出QEventLoop, ChildThread 线程也退出来了
常用错误整理
1:ASSERT failure in QCoreApplication::sendEvent: “Cannot send events to objects owned by a different thread. Current thread 2c3fef98. Receiver ‘’ (of type ‘Pictures’) was created in thread 2e2a7d98”,
原因分析:
postEvent: 可以给别的线程发送事件。事件会在目的对象所属的线程中运行。这是一个异步接口。
sendEvent: 仅用于同一个线程之间发送事件。目的对象必须与当前线程一样。这是一个同步接口。假如发送给属于另一个线程的对象,会报错:
object不在movetothread 上的object实例化的话,直接movetothread就会出现这个错误。
2: QObject::moveToThread: Current thread (0xxxxx) is not the object’s thread (0x2c36ef98).Cannot move to target thread (0xxxxxx)
原因分析:
在Qt中,如果要切换对象的线程,不能到了目标线程里再调用moveToThread,此举会导致切换线程失败
3: QObject::~QObject: Timers cannot be stopped from another thread
原因分析:
1、 不能夸线程启动定时器和停止定时器
2、 不能夸线程启动一个定时器关联的对象,但在另一个线程释放(析构)此和定时器关联的对象(相当于1>的情况不能在其他线程停止定时器).
不一定与定时器有关,在开始线程的时候实例化一个对象QOject,加了(this),但是打印完成,这线程就结束了,所以在关闭软件的时候,去DELETE 子线程的QOject,就报这个错。
4: QObject: Cannot create children for a parent that is in a different thread.
(Parent is QAxObject(0x34143e90), parent’s thread is QThread(0x2beeef98), current thread is QThread(0x320709e0)
原因分析:
使用线程的时候。传递一个在UI线程实例化了的对象QAxObject进来并运用
5: QObject::moveToThread: Cannot move objects with a parent
6: QObject::moveToThread: Widgets cannot be moved to a new thread
原因分析:
只有继承QOject才能moveToThread
7: QThread: Destroyed while thread is still running
原因分析:
线程未结束就delete线程类
解决办法:
在继承QThread的类的析构函数中添加 wait();quit();
借鉴大神网址:
大神一
大神二
QThread 错误整理
主要是弄懂大神想法,自己动手敲代码弄清楚。如有侵权,请联系我更改。