前言
说到QFutureWatcher,就不得不说到Qt Concurrent和QFuture,Qtconcurrent是Qt的高级接口,为什么说是高级呢?因为有的时候,对于一些耗时的运行任务,我们做法往往是直接丢给QThread自己去run,或者搞一个工作类继承QObject,然后movetoThread。当然这些方法并没有错,但是我们只是做一次耗时运算,仅仅一次,开线程未免太过奢侈了吧。。。
那有没有更加优雅的方法呢,当然,Qt早就想到了,那就是Qt Concurrent,它提供了一整套异步运算的高级API,如果有兴趣,你可以查询Qt Assistant,有很详细的介绍,本文只是对异步运算任务进行监控。
提示:以下是本篇文章正文内容,下面案例可供参考
一、QFuture及QFutureWatcher
QFuture类表示异步计算的结果。既然表示结果,那么应该什么时候去获取,以及如何获取结果呢?不要慌,听我慢慢讲,由于QFuture不是继承QObject,即不是QObject的子类,那么也就意味着不能使用信号和槽机制,那就更离谱了,不能用信号槽,而且是异步运算的,那咋整?
答案就是QFutureWatcher,QFutureWatcher类允许使用信号和插槽监控QFuture。
QFutureWatcher提供有关QFuture的信息和通知。使用setFuture()函数开始监视特定的QFuture。函数的作用是:返回带有setFuture()的future集。
1.获取
关于QFuture的创建,目前最新版本的是Qt6.2,据说可以自己创建QFuture对象,博主这边使用的还是Qt5.9,必须使用Qt concurrent的API返回一个QFuture的对象,代码如下:
QFuture<void> fu = QtConcurrent::run([&](){
QStringList ned = alist;
qDebug()<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")<<"计算开始";
for(int i =0;i<ned.size();++i){
QString as = ned.at(i);
quint32 val = JQChecksum::crc32(as,as.split("/").last());
qDebug()<<val;
}
qDebug()<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")<<"计算结束";
});
这是使用run作为异步运算的接口,具体内容由lambda表达式给出。这是一般的写法,如果需要做异步通知的话,可以在lambd最后自定义一个信号发出来,通知外面(主线程)活干完了。但是根据Qt的官方文档,这样说的:
QFuture also offers ways to interact with a runnning computation. For instance, the computation can be canceled with the cancel() function. To pause the computation, use the setPaused() function or one of the pause(), resume(), or togglePaused() convenience functions. Be aware that not all asynchronous computations can be canceled or paused. For example, the future returned by QtConcurrent::run() cannot be canceled; but the future returned by QtConcurrent::mappedReduced() can.
翻译一下:QFuture还提供了与运行计算交互的方法。例如,可以使用cancel()函数取消计算。要暂停计算,请使用setPaused()函数或pause()、resume()或togglePaused()函数之一。请注意,并非所有异步计算都可以取消或暂停。例如,QtConcurrent::run()返回的future不能取消;但是QtConcurrent::mappedReduced()可以
2.使用mappedReduced
代码如下:
void MainWindow::on_pushButton_clicked()
{
QList<QString> alist;
QDir dir("E:\\MODLE_PG_SVN_SRC\\bin\\Data_Files\\Patterns\\res1080_2340");
QFileInfoList infolist = dir.entryInfoList(QDir::Files);
qDebug()<<infolist.size();
for(auto item : infolist){
alist<<item.absoluteFilePath();
}
#if 1
int size = alist.size();
qDebug()<<"需要计算的图片数:"<<size;
ui->progressBar->setMaximum(size);
QThreadPool::globalInstance()->setMaxThreadCount(size>10?10:size);
QEventLoop loop;
QFutureWatcher<QList<quint32>> watcher;
QFuture<QList<quint32>> f = QtConcurrent::mappedReduced(alist,func2,sum);
connect(&watcher,&QFutureWatcher<QList<quint32>>::finished,[&](){
showlog("并行计算结束");
qDebug()<<f.result();
loop.quit();
QMessageBox::about(this,"tips","并行计算结束");
});
connect(&watcher,&QFutureWatcher<QList<quint32>>::progressValueChanged,[&](int value){
qDebug()<<"计算进度:"<<QString::number(value);
ui->progressBar->setValue(value);
});
watcher.setFuture(f);
showlog("开始计算进入loop等待...");
loop.exec();
showlog("结束等待...");
#else
QFuture<void> fu = QtConcurrent::run([&](){
QStringList ned = alist;
qDebug()<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")<<"计算开始";
for(int i =0;i<ned.size();++i){
QString as = ned.at(i);
quint32 val = JQChecksum::crc32(as,as.split("/").last());
qDebug()<<val;
}
qDebug()<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")<<"计算结束";
});
#endif
}
简单说明一下,我这边任务时计算图片的CRC值,有多少张呢,大概好几百张,如果根据上述1的方法,for循环来一张张计算,显然速度慢,当然也不是不行,如果你输了少的话,倒是没什么问题。或者说对等待时间没有要求的话,是可以的。
还是解释一下吧,QThreadPool是全局线程池,可以修改每次并行任务计算的线程数,这里修改成10.然后开启一个事件循环,定义一个QFutureWatcher,并且用mappedReduced返回一个QFuture对象,mappedReduced是非阻塞的,如果你要阻塞,可以用带block的。此处就不展开了,可以自行查询。mappedReduced有三个参数,第一个是list列表,第二个是任务函数,第三个是对每一个结果做一个操作的。
原型如下:
QFuture<T> QtConcurrent::mappedReduced(ConstIterator begin, ConstIterator end, MapFunction mapFunction, ReduceFunction reduceFunction, QtConcurrent::ReduceOptions reduceOptions = UnorderedReduce | SequentialReduce)
官方解释:按顺序为每个项调用mapFunction一次。每个mapFunction的返回值都传递给reduceFunction。
具体定义如下:
quint32 func2(QString a){
qDebug()<<"current thread ID:"<<QThread::currentThreadId();
int val = JQChecksum::crc32(a,a.split("/").last());
return val;
}
void sum(QList<quint32>& L, const quint32& b){
L<<b;
}
每次计算结束,都将结果存入到QList中,因此所有任务时,可以用QFuture::result()接口获取到这个List
3.进度
connect(&watcher,&QFutureWatcher<QList<quint32>>::progressValueChanged,[&](int value){
qDebug()<<"计算进度:"<<QString::number(value);
ui->progressBar->setValue(value);
});
QFutureWatcher有个信号progressValueChanged,可以实时传递异步任务完成的数量,不得不感叹,确实强大,我这边是直接绑定到QProgress进度条了。
注意不要忘了,将QFuture对象计入到watcher
watcher.setFuture(f);