在Qt中,有时候我们会遇到这样一种情况,需要执行一个很长时间的操作,这时候我们的主界面就会卡住。我们的通常做法就是把这个很长时间的操作扔到线程里去处理,可以使用标准库中的线程也可以使用QThread。
如果我们要在这个很长时间的操作之后,在UI上显示一些东西,或者改变一些UI上的控件的状态。这种时候标准库的线程就不是很好用了,通常这种时候我们会使用QThread,创建一个新的类继承QObject,然后再这个新的类里面写一堆信号和槽,和主线程通讯传递消息改变UI界面。但是这种太麻烦了,每次都要新创建一个类和一堆信号,十分不好管理。
在查了资料后,我发现了Qt有提供专门并发的类QtConcurrent,以及接收异步计算结果的QFuture类。在这里记录一下QtConcurrent和QFuture的使用。
介绍:
Concurrent是并发的意思,而QtConcurrent同std一样,是一个命名空间(namespace),想使用它需要先在Project工程文件中导入模块,并包含头文件QtConcurrent/QtConcurrent。
QT += concurrent
#include <QtConcurrent/QtConcurrent>
QtConcurrent提供了一些高级的 API,使得在编写多线程的时候,无需使用低级线程原语,如读写锁,等待条件或信号。使用QtConcurrent编写的程序会根据可用的处理器内核数自动调整使用的线程数。
QtConcurrent中使用最多的是它的run()函数,每调用一次QtConcurrent::run()函数,就会新建立一个线程运行我们让它执行的函数。run()函数的返回值QFuture类型的,run()函数是有很多重载,这里就简单讲几个常用的。
QtConcurrent::run示例:
调用全局函数:
函数原型:
QFuture<T> QtConcurrent::run(Function func, ...)
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QThread>
#include <QFuture>
#include <QtConcurrent/QtConcurrent>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
QString func(QString content)
{
QString str = QString("%1 %2 %3.").arg(__FUNCTION__).arg(content).arg(quintptr(QThread::currentThreadId()));
return str;
}
void MainWindow::on_pushButton_clicked()
{
QFuture<QString> fut1 = QtConcurrent::run(func, QString("Thread_1"));// 用QFuture获取该函数的运行结果,参数2:向func函数传递的参数
QFuture<QString> fut2 = QtConcurrent::run(func, QString("Thread_2"));
QString result1 = fut1.result();
QString result2 = fut2.result();
qDebug() << result1;
qDebug() << result2;
fut1.waitForFinished();// waitForFinished()保证线程执行完毕
fut2.waitForFinished();
}
这里使用QFuture的result()函数获取QtConcurrent::run()执行的函数的返回值,然后打印出来,打印结果如下:
调用匿名函数:
上面的示例中的func也可以改成匿名函数,只是写法上不同,结果都是一样的:
QFuture <QString> future = QtConcurrent::run([=](){
QString str = QString("%1 %2 %3.").arg(__FUNCTION__).arg("Thread_1").arg(quintptr(QThread::currentThreadId()));
return str;
});
QFuture <QString> future2 = QtConcurrent::run([=](){
QString str = QString("%1 %2 %3.").arg(__FUNCTION__).arg("Thread_2").arg(quintptr(QThread::currentThreadId()));
return str;
});
调用成员函数:
同样的,QtConcurrent::run()也可以调用成员函数,第一个参数必须是一个const引用或一个指向该类实例的指针,第二个参数是函数指针:
QString MainWindow::func(QString content)
{
QString str = QString("%1 %2 %3.").arg(__FUNCTION__).arg(content).arg(quintptr(QThread::currentThreadId()));
return str;
}
QFuture<QString> fut1 = QtConcurrent::run(this, &MainWindow::func, QString("Thread_1"));
QFuture<QString> fut2 = QtConcurrent::run(this, &MainWindow::func, QString("Thread_2"));
调用其他类的成员函数:
调用其他类的成员函数,包括Qt提供的函数也是同样的方法:
QByteArray bytearray = "hello,world";
QFuture<QList<QByteArray>> future = QtConcurrent::run(bytearray, &QByteArray::split, ',');
使用线程池中的线程调用函数:
// 函数原型
template <typename T> QFuture<T> QtConcurrent::run(QThreadPool *pool, Function function, ...)
使用QFutureWatcher监视线程:
QFuture 表示异步计算的结果,QFutureWatcher 则允许使用信号和槽监视 QFuture,也就是说,QFutureWatcher 是为 QFuture 而生的。
示例,开始计算并当完成时通过槽获取结果:
// 实例化对象,并连接槽到 finished() 信号,等线程中函数完成后就会触发连接的槽。
MyClass myObject;
QFutureWatcher<int> watcher;
connect(&watcher, &QFutureWatcher<QVector<complex<double>>>::finished, &myObject, &MyClass::handleFinished);
// 开始计算
QFuture<int> future = QtConcurrent::run(...);
watcher.setFuture(future);
匿名函数的写法:
QFutureWatcher<QVector<complex<double>>>* pwatcher = new QFutureWatcher<QVector<complex<double>>>;
QFuture<QVector<complex<double>>> future = QtConcurrent::run([=]() {
QVector<complex<double>> result;
// 费时操作在这里执行,之后返回QVector<complex<double>>类型的结果
return result;
});
connect(pwatcher, &QFutureWatcher<QVector<complex<double>>>::finished, this, [=]() {
// 使用pwatcher->result()获取在QtConcurrent::run()中的返回值
QVector<complex<double>> img2result = pwatcher->result();
// 执行费时操作完成之后的操作,例如改变UI界面的控件
});
pwatcher->setFuture(future);