继承QRunnable+QThreadPool实现多线程的方法个人感觉使用的相对较少,在这里只是简单介绍下使用的方法。我们可以根据使用的场景来选择方法。
此方法和QThread的区别:
与外界通信方式不同。由于QThread是继承于QObject的,但QRunnable不是,所以在QThread线程中,可以直接将线程中执行的结果通过信号的方式发到主程序,而QRunnable线程不能用信号槽,只能通过别的方式,等下会介绍;
启动线程方式不同。QThread线程可以直接调用start()函数启动,而QRunnable线程需要借助QThreadPool进行启动;
资源管理不同。QThread线程对象需要手动去管理删除和释放,而QRunnable则会在QThreadPool调用完成后自动释放。
接下来就来看看QRunnable的用法、使用场景以及注意事项;
一、步骤要使用QRunnable创建线程,步骤如下:
继承QRunnable。和QThread使用一样, 首先需要将你的线程类继承于QRunnable;
重写run函数。还是和QThread一样,需要重写run函数;
使用QThreadPool启动线程。
二、实例继承于QRunnable的类:
#ifndef INHERITQRUNNABLE_H #define INHERITQRUNNABLE_H #include <QRunnable> #include <QWidget> #include <QDebug> #include <QThread> class CusRunnable : public QRunnable { public: explicit CusRunnable(){ } ~CusRunnable(){ qDebug() << __FUNCTION__; } void run(){ qDebug() << __FUNCTION__ << QThread::currentThreadId(); QThread::msleep(1000); } }; #endif // INHERITQRUNNABLE_H主界面类:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include "ui_mainwindow.h" #include "InheritQRunnable.h" #include <QThreadPool> #include <QDebug> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0) : QMainWindow(parent), ui(new Ui::MainWindow){ ui->setupUi(this); m_pRunnable = new CusRunnable(); qDebug() << __FUNCTION__ << QThread::currentThreadId(); QThreadPool::globalInstance()->start(m_pRunnable); } ~MainWindow(){ qDebug() << __FUNCTION__ ; delete ui; } private: Ui::MainWindow *ui; CusRunnable * m_pRunnable = nullptr; }; #endif // MAINWINDOW_H直接运行以上实例,结果输出如下:
MainWindow 0x377c run 0x66ac ~CusRunnable我们可以看到这里打印的线程ID是不同的,说明是在不同线程中执行,而线程执行完后就自动进入到析构函数中, 不需要手动释放。
三、启动线程的方式上面我们说到要启动QRunnable线程,需要QThreadPool配合使用,而调用方式有两种:全局线程池和非全局线程池。
(1)使用全局线程池启动
QThreadPool::globalInstance()->start(m_pRunnable);(2)使用非全局线程池启动
该方式可以控制线程最大数量, 以及其他设置,比较灵活,具体参照帮助文档。
QThreadPool threadpool; threadpool.setMaxThreadCount(1); threadpool.start(m_pRunnable); 四、如何与外界通信前面我们提到,因为QRunnable没有继承于QObject,所以没法使用信号槽与外界通信,那么,如果要在QRunnable线程中和外界通信怎么办呢,通常有两种做法:
使用多继承。让我们的自定义线程类同时继承于QRunnable和QObject,这样就可以使用信号和槽,但是多线程使用比较麻烦,特别是继承于自定义的类时,容易出现接口混乱,所以在项目中尽量少用多继承。
使用QMetaObject::invokeMethod。
接下来只介绍使用QMetaObject::invokeMethod来通信:
QMetaObject::invokeMethod 函数定义如下:
static bool QMetaObject::invokeMethod( QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument(Q_NULLPTR), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument());该函数就是尝试调用obj的member函数,可以是信号、槽或者Q_INVOKABLE声明的函数(能够被Qt元对象系统唤起),只需要将函数的名称传递给此函数,调用成功返回true,失败返回false。member函数调用的返回值放在ret中,如果调用是异步的,则不能计算返回值。你可以将最多10个参数(val0、val1、val2、val3、val4、val5、val6、val7、val8和val9)传递给member函数,必须使用Q_ARG()和Q_RETURN_ARG()宏封装参数,Q_ARG()接受类型名 + 该类型的常量引用;Q_RETURN_ARG()接受一个类型名 + 一个非常量引用。
QMetaObject::invokeMethod可以是异步调用,也可以是同步调用。这取决与它的连接方式Qt::ConnectionType type:
如果类型是Qt::DirectConnection,则会立即调用该成员,同步调用。
如果类型是Qt::QueuedConnection,当应用程序进入主事件循环时,将发送一个QEvent并调用该成员,异步调用。