Qt多线程

序言

多线程一般用在:
在QT的 UI 编程中, 如果有一个函数消耗的时间特别长, 并且运行于主线程, 那么界面的响应会很不灵敏.。比如说 如果该函数执行时间很长, 为了通知任务的进度, 一般会使用进度条. 但有时候无法准确的使用进度条, 比如在数据库操作中, 为了提高读写数据库的性能, 通常会采用事务操作, 多个读写数据库的操作合并成了一个事务, 此时如何设置进度条的进度值。

Qt 多线程常见得三种方法:

  1. 继承自QThread.
  2. moveToThread.
  3. QtConcurrent::run().

方法

继承QThread

说明

  1. 自定义继承QThread类,需要重写run函数,去依据需要实现自己的方法。
  2. 调用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

说明

  1. 定义一个继承于QObject的worker类,在worker类中定义一个槽函数,这个函数中定义线程需要做的工作.
  2. 在要使用线程的controller类中,新建一个QThread的对象和woker类对象,使用moveToThread()方法将worker对象的事件循环全部交由QThread对象处理.
  3. 建立相关的信号函数和槽函数进行连接,然后发出信号触发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()

说明

需要了解如下类 :

  1. QtConcurrent
  2. QFutureWatcher
  3. 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 错误整理
主要是弄懂大神想法,自己动手敲代码弄清楚。如有侵权,请联系我更改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

道阻且长,行则降至

无聊,打赏求刺激而已

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值