QT 中如何正确使用QThread

QThread看似类似于C++11的std::thread,实则大不相同。std::thread可以运行一个用户指定的函数;可以说比较的底层。而QThread则根本没有给出任何接口让你有机会指定你想运行的函数,因为它根本就不是为了运行用户指定的函数而生的,而是作为一个线程管理者,而很奇怪的是,这个管理者并不让你有机会指定你想运行的函数。也许有人说,继承QThread,然后重载run()函数,这不就可以了?事实上,这并不是正确的做法,也不是QThread存在的初衷。

那么,究竟怎样是正确的使用QThread呢?怎样才能让用户指定的函数作为一个独立线程运行起来呢?这篇QT的博客给出了详尽的解释。本文不打算再把此博文翻译一遍,而是打算给出一点自己的代码,当然,也是在原博文基础上做了一点点修改。最后指出一个原博文中没有提到的小坑。

首先,简单介绍一下使用QThread的正确方法:

第一步,写一个自己的类,继承QObject,然后在此类中定义一个自己想运行的方法,起什么名字都可以,没有限制。比如,在下文中,笔者将这个方法命名为void run().

第二步,因为此自定义类是继承了QObject的,所以可以调用moveToThread(QThread)方法,将整个自定义类的对象移到该QThread中。

第三步,做一系列的connect,主要是将QThread类的signal和自定义类的slot连接起来,详情可参见下面的代码。

第四步,调用QThread的start()启动新线程。

第五步,调用QThread的wait()方法使主线程阻塞。

具体详情请参见如下代码。

介绍一下整个工程。工程是QT的Console工程,除了.pro文件之外,还有三个文件:worker.h, worker.cpp和main.cpp. 具体代码如下:

1. TestQMutexQThread.pro

  1. QT += core  
  2. QT -= gui  
  3.   
  4. CONFIG += c++11  
  5.   
  6. TARGET = TestQSemaphore  
  7. CONFIG += console  
  8. CONFIG -= app_bundle  
  9.   
  10. TEMPLATE = app  
  11.   
  12. SOURCES += main.cpp \  
  13.     worker.cpp  
  14.   
  15. HEADERS += \  
  16.     worker.h  
QT += core
QT -= gui

CONFIG += c++11

TARGET = TestQSemaphore
CONFIG += console
CONFIG -= app_bundle

TEMPLATE = app

SOURCES += main.cpp \
    worker.cpp

HEADERS += \
    worker.h


2. worker.h

  1. #ifndef MYTHREAD_H  
  2. #define MYTHREAD_H  
  3.   
  4. #include <QThread>  
  5. #include <QMutex>  
  6. #include <QUuid>  
  7. #include <QFile>  
  8. #include <QDir>  
  9.   
  10.   
  11. class Worker : public QObject  
  12. {  
  13.     Q_OBJECT  
  14.   
  15. private:  
  16.     static int myValue;  
  17.     static QMutex leds_mutex;  
  18.     QString m_id;  
  19.   
  20. public:  
  21.     Worker(QObject* parent = nullptr);  
  22.     ~Worker();  
  23.   
  24. public slots:  
  25.     void run();  
  26.   
  27. signals:  
  28.     void finished();  
  29.     void error(QString err);  
  30. };  
  31.   
  32. #endif // MYTHREAD_H  
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QMutex>
#include <QUuid>
#include <QFile>
#include <QDir>


class Worker : public QObject
{
    Q_OBJECT

private:
    static int myValue;
    static QMutex leds_mutex;
    QString m_id;

public:
    Worker(QObject* parent = nullptr);
    ~Worker();

public slots:
    void run();

signals:
    void finished();
    void error(QString err);
};

#endif // MYTHREAD_H

3. worker.cpp

  1. #include "worker.h"  
  2. #include <QDebug>  
  3.   
  4.   
  5. int Worker::myValue = 0;  
  6. QMutex Worker::leds_mutex;  
  7.   
  8. Worker::Worker(QObject* parent)  
  9.     : QObject(parent)  
  10.     , m_id(QUuid::createUuid().toString())  
  11. {  
  12. }  
  13.   
  14. Worker::~Worker()  
  15. {  
  16. }  
  17.   
  18. void Worker::run()  
  19. {  
  20.     int count = 0;  
  21.     while (count < 5) {  
  22.         ++count;  
  23.         Worker::leds_mutex.lock();  
  24.   
  25.         QFile file;  
  26.         QDir::setCurrent("C:/Users/YourUserName/Desktop");  
  27.         file.setFileName("1.txt");  
  28.         if (!file.open(QIODevice::Append | QIODevice::Text)) {  
  29.             qCritical() << "Failed to open file";  
  30.             Worker::leds_mutex.unlock();  
  31.             break;  
  32.         }  
  33.   
  34.         QTextStream out(&file);  
  35.         out     << "id: " << m_id << ", myValue = " << Worker::myValue << '\n';  
  36.         qInfo() << "id: " << m_id << ", myValue = " << Worker::myValue;  
  37.         ++Worker::myValue;  
  38.   
  39.         Worker::leds_mutex.unlock();  
  40.     }  
  41.   
  42.     emit finished();  
  43. }  
#include "worker.h"
#include <QDebug>


int Worker::myValue = 0;
QMutex Worker::leds_mutex;

Worker::Worker(QObject* parent)
    : QObject(parent)
    , m_id(QUuid::createUuid().toString())
{
}

Worker::~Worker()
{
}

void Worker::run()
{
    int count = 0;
    while (count < 5) {
        ++count;
        Worker::leds_mutex.lock();

        QFile file;
        QDir::setCurrent("C:/Users/YourUserName/Desktop");
        file.setFileName("1.txt");
        if (!file.open(QIODevice::Append | QIODevice::Text)) {
            qCritical() << "Failed to open file";
            Worker::leds_mutex.unlock();
            break;
        }

        QTextStream out(&file);
        out     << "id: " << m_id << ", myValue = " << Worker::myValue << '\n';
        qInfo() << "id: " << m_id << ", myValue = " << Worker::myValue;
        ++Worker::myValue;

        Worker::leds_mutex.unlock();
    }

    emit finished();
}

4. main.cpp

  1. #include <QCoreApplication>  
  2. #include <QDebug>  
  3. #include <QScopedPointer>  
  4. #include "worker.h"  
  5.   
  6.   
  7. int main(int argc, char *argv[])  
  8. {  
  9.     QCoreApplication a(argc, argv);  
  10.   
  11.     QThread* t1 = new QThread;  
  12.     QThread* t2 = new QThread;  
  13.   
  14.     Worker* w1 = new Worker;  
  15.     Worker* w2 = new Worker;  
  16.   
  17.     w1->moveToThread(t1);  
  18.     w2->moveToThread(t2);  
  19.   
  20.     QObject::connect(t1, &QThread::started,  w1, &Worker::run);  
  21.     QObject::connect(w1, &Worker::finished,  t1, &QThread::quit);  
  22.     QObject::connect(w1, &Worker::finished,  [](){qInfo() << "w1 quit......";});  
  23.     QObject::connect(w1, &Worker::finished,  w1, &Worker::deleteLater);  
  24.     QObject::connect(t1, &QThread::finished, t1, &QThread::deleteLater);  
  25.   
  26.     QObject::connect(t2, &QThread::started,  w2, &Worker::run);  
  27.     QObject::connect(w2, &Worker::finished,  t2, &QThread::quit);  
  28.     QObject::connect(w2, &Worker::finished,  [](){qInfo() << "w2 quit......";});  
  29.     QObject::connect(w2, &Worker::finished,  w2, &Worker::deleteLater);  
  30.     QObject::connect(t2, &QThread::finished, t2, &QThread::deleteLater);  
  31.   
  32.   
  33.     t1->start();  
  34.     t2->start();  
  35.   
  36.     t1->wait(3);  
  37.     t2->wait(3);  
  38.   
  39.     qInfo() << "At the end of main thread...";    
  40.   
  41.     return 0;  
  42. }  
#include <QCoreApplication>
#include <QDebug>
#include <QScopedPointer>
#include "worker.h"


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QThread* t1 = new QThread;
    QThread* t2 = new QThread;

    Worker* w1 = new Worker;
    Worker* w2 = new Worker;

    w1->moveToThread(t1);
    w2->moveToThread(t2);

    QObject::connect(t1, &QThread::started,  w1, &Worker::run);
    QObject::connect(w1, &Worker::finished,  t1, &QThread::quit);
    QObject::connect(w1, &Worker::finished,  [](){qInfo() << "w1 quit......";});
    QObject::connect(w1, &Worker::finished,  w1, &Worker::deleteLater);
    QObject::connect(t1, &QThread::finished, t1, &QThread::deleteLater);

    QObject::connect(t2, &QThread::started,  w2, &Worker::run);
    QObject::connect(w2, &Worker::finished,  t2, &QThread::quit);
    QObject::connect(w2, &Worker::finished,  [](){qInfo() << "w2 quit......";});
    QObject::connect(w2, &Worker::finished,  w2, &Worker::deleteLater);
    QObject::connect(t2, &QThread::finished, t2, &QThread::deleteLater);


    t1->start();
    t2->start();

    t1->wait(3);
    t2->wait(3);

    qInfo() << "At the end of main thread...";  

    return 0;
}

好了,源代码就这么多。自己定义的这个Worker::run()方法到底是如何作为一个线程跑起来的呢?关键点就在于main.cpp当中的这一堆的connect语句了。 首先,因为Worker类是继承了QObject类的,所以它可以调用moveToThread(QThread)方法将自身移到一个指定的QThread中去,而这个QThread要想真正运行起来,必须要调用start()方法。一旦调用了QThread的start方法, 就会有QThread的started信号(signal)发射出去,于是便会trigger这个Worker类的run()函数了。

以上这一段就是全部的精华了。

最后一个小坑:注意:在主线程中,有t1->wait(3)和t2->wait(3)这2句话。去查一下手册吧,wait()函数到底是干嘛的。其作用主要是使得主线程阻塞住,等待指定线程,即t1和t2的执行结束,或者超时。这都很好理解。注意,如果没有这2句wait(),主线程由于执行的太快,当子线程还没执行结束,主线程就已经结束退出进而导致整个进程结束了,那子线程自然也就终止了。不过,以上这些还不是笔者说的小坑。这里的小坑是,wait()必须给它填一个参数!如果不填参数的话,当然它有一个默认的参数,但是会导致子线程即使结束,主线程也仍然并将永远处于阻塞状态。这个不知道是否是QT的一个bug.但是,给wait()填一个数字作为参数,即使填上300,代表300秒,主线程也不会真正等上5分钟,而只是一旦子线程结束,主线程也就会解除阻塞,继续往下执行了。


(完)

  • 10
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值