QThread

参考: https://www.cnblogs.com/xia-weiwen/p/10306089.html

继承QThread

//copyFileThread.h
CopyFileThread: public QThread    //这种方法下,CopyFileThread除了run函数,其它函数还会在主线程内执行
{
    Q_OBJECT
public:
    CopyFileThread(QObject * parent = 0);

protected:
    void run(); // 新线程入口
// 省略掉一些内容
}


//copyFileThread.cpp
void CopyFileThread::run()
{
    // 新线程入口
    // 初始化和操作放在这里
}


// mainwindow.h中
CopyFileThread * m_cpyThread;

// mainwindow.cpp中
m_cpyThread = new CopyFileThread;
...
m_cpyThread->start();

线程开始执行后,就进入run()函数,执行复制文件的操作。而此时,主线程的显示和操作都不受影响。
如果需要进行对复制过程中可能发生的事件进行处理,例如界面显示复制进度、出错返回等等,应该从CopyFileThread中发出信号(signal),并事先连接到mainwindow的槽,由这些槽函数来处理事件。

使用技巧

  • 一般调用quit()函数之后可以紧接着调用wait()函数确保线程退出。
  • sleep()等让线程休眠的函数不需要调用,因为Qt中线程是事件驱动机制。但是如果是继承的QTHread类,在run()函数中使用了无限循环的方式,可以考虑msleep()函数来使线程休息一段时间,一般为1毫秒。

moveToThread()

class FileCopier : public QObject
{
    Q_OBJECT
public:
    explicit FileCopier(QObject *parent = 0);

public slots:
    void startCopying();
    void cancelCopying();
}
// mainwindow.h中
private:
    FileCopier* m_copier;

// mainwindow.cpp中,初始化时
    m_copier = new FileCopier;
// mainwindow.h中
signals:
    void startCopyRsquested();
private:
    QThread * m_childThread; // m_copier将被移动到此线程执行

// mainwindow.cpp中,初始化时
    m_childThread = new QThread; // 子线程,本身不负责复制
    m_copier->moveToThread(m_childThread); // 将实例移动到新的线程,实现多线程运行
    m_childThread->start(); // 启动子线程

注意一定要记得启动子线程,否则线程没有运行,m_copier的功能也无法执行。
要开始复制,需要使用信号-槽机制,触发FileCopier的槽函数实现。因此要事先定义信号并连接:

槽函数要移到子线程,且触发信号后才能执行。

// mainwindow.h中
signals:
    void startCopyRsquested();
// mainwindow.cpp中,初始化时
// 使用信号-槽机制,发出开始指令
    connect(this, SIGNAL(startCopyRsquested()), m_copier, SLOT(startCopying()));
...
    emit startCopyRsquested(); // 发送信号

使用Qtconcurrent

QT += core concurrent
CONFIG += c++11
QtConcurrent::run([](){
   qDebug()  << QThead::currentThread();
    
    //同样可以使用connect
    //不需要信号、直接调用槽函数
    QMetaObject::invokeMethod(&sender, "funcName", Qt::QueuedConnection);//貌似还是在主线程?
});

多线程配合lambda,代码量最少

QThread退出的方式

thread->requestInterruption();
thread->quit();
thread->wait();

1. run()函数执行完成,线程自动退出。

  • 方法一:通过设置一个bool变量控制run()函数中的while循环,注意对bool变量的互锁;
  • 方法二:通过isInterruptionRequested()和requestInterruption()控制run()函数中的while循环,内部以实现互锁;
void run() override
{
    while(isThreadActive) //或者while(isInterruptionRequested())
    {
        //do something...
    }
}

//在run()函数外设置isThreadActive = false或者调用requestInterruption()函数来退出while循环,结束run()函数的运行,从而退出子线程

2.QObject::moveToThread
通过moveToThread()创建的子线程如要结束,则必须调用quit()函数来退出,但是调用quit()函数并不会立即结束子线程,而是在等待子线程中槽函数运行完成之后,子线程才会退出。槽函数中while循环的控制可类似于上面run()函数的控制。
注意:链接到子线程中执行的槽函数会根据触发及链接的先后顺序,依次执行,如

connect(this, &Controller::operate, worker, &Worker::doWork);
connect(this, &Controller::operate, worker, &Worker::doWork2);
//则子线程会在执行完成doWork槽函数之后再去执行doWord2槽函数.

more : https://blog.csdn.net/qq_31348733/article/details/97373786

问题

1. 子线程中能不能进行UI操作?

Qt中的UI操作,比如QMainWindow、QWidget之类的创建、操作,只能位于主线程!
这个限制意味着你不能在新的线程中使用QDialog、QMessageBox等。比如在新线程中复制文件出错,想弹出对话框警告?可以,但是必须将错误信息传到主线程,由主线程实现对话框警告。
因此一般思路是,主线程负责提供界面,子线程负责无UI的单一任务,通过“信号-槽”与主线程交互。

2. QThread中的哪些代码属于子线程?

QThread,以及继承QThread的类(以下统称QThread),他们的实例都属于新线程吗?答案是:不。
需要注意的是,QThread本身的实例是属于创建该实例的线程的。比如在主线程中创建一个QThread,那么这个QThread实例本身属于主线程。当然,QThread会开辟一个新线程(入口是run()),但是QThread本身并不属于这个新线程。也就是说,QThread本身的成员都不属于新线程,而且在QThread构造函数里通过new得到的实例,也不属于新线程。这一特性意味着,如果要实现多线程操作,那么你希望属于新线程的实例、变量等,应该在run()中进行初始化、实例化等操作。本文给出的例子就是这样操作的。
如果你的多线程程序运行起来,会出现关于thread的报警,思考一下,各种变量、实例是不是放对了位置,是不是真的位于新的线程里。

3. 怎么查看是不是真的实现了多线程?

可以打印出当前线程。对于所有继承自QObject的类,例如QMainwindow、QThread,以及自定义的各种类,可以调用QObject::thread()查看当前线程,这个函数返回的是一个QThread的指针。例如用qDebug()打印:
在mainwindow.cpp的某个函数里、QThread的run()函数里、自定义类的某个函数里,写上:

qDebug() << "Current thread:" << thread(); QThread::currentThread()
对比不同位置打印的指针,就可以知道它们是不是位于同一个线程了。

posted @ 2020-02-23 11:42  friedCoder  阅读(256)  评论(0)    收藏  举报