使用QFuture类监控异步计算的结果

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Amnes1a/article/details/65630701
在Qt中,为我们提供了好几种使用线程的方式,除了最基本的QThread类之外,还有QRunnable抽象类,类似于Java的runnable接口,还可以使用moveToThread() 函数,还有更高级的QtConcurrent框架。而今天,我们要看的QFuture就是和QtConcurrent框架API配合使用的一个类。新来看Qt帮助文档对这个类的详细介绍。

QFuture类用来表示一个异步计算的结果,而该异步计算通常就是由Qt Concurrent框架中的相关函数开启的。QFuture允许线程同步的获得一个或多个在将来某个时间点才准备好的结果。这些结果可以是任何具有默认构造函数和拷贝构造函数的类型。如果在调用该类的result()、resultAt() 或 results() 函数时,某个结果还不可用,QFuture会等待直到该结果可用,即可得到。当然,你也可以使用isResultReadyAt() 函数来判断某个结果是否已经准备好了。对于那些要获得多个结果的QFuture对象来说,resultCount() 函数会返回可以得到的连续结果的个数。这也意味着,我们在0和resultCount() 之间对QFuture对象进行遍历,总是安全的。并且,处理使用刚才说的下标外,QFuture还提供了Java类型的和STL类型的迭代器供我们使用。

使用QFuture对象,我们也可以和正在运行中的异步计算进行交互。例如,可以使用cancel() 函数取消一个异步计算;使用setPaused()、pause()、resume()、或者 togglePaused() 函数暂停一个异步计算。但要记住,并不是所有的异步计算都能被取消或暂停。例如,QtConcurrent()::run() 方法返回的future不能被取消,但是QtConcurrent::mappedReduced() 函数返回的future就可以。

除了刚才说的暂停操作,我们还可以获得异步计算的当前进度信息,progressValue()、progressMinimum()、progressMaximum()和progressText() 函数可以帮我们提供这些信息。

一个比较重要的函数就是waitForFinished(),该函数会导致调用线程阻塞来等待异步计算结束,以确保所有的结果都是可用的。

上面讲到,我们可以手工的暂停或取消一个异步计算,相对的,QFuture也为我们提供了查询当前计算状态的方法。比如,isCanceled(),isStarted(),isFinished(),isRunning()和 isPaused()函数。

QFuture是一个轻量级的引用计数类,所以它可以被当做参数值传递。

其实,在构建QFuture的对象时,我们会同时指定一个模板参数,表示QFuture要处理的结果的类型。其中,QFuture<void>是一个特化的不包含获取结果函数的QFuture对象。内可以将一个QFuture<void>对象赋值给一个QFuture<T>对象,反过来亦可。这对于那些只关注异步计算的状态或进度信息,而不关注实际结果的QFuture对象来说是及其有用的。

但这个类不支持信号和槽,若想使用信号和槽来和异步计算进行通信,需要使用QFutureWatcher类。

下面,我们再来看一下QFuture类中常用的一些函数。

QFuture::QFuture()
QFuture::QFuture(const QFuture &other)
QFuture::~QFuture()
构造函数到没什么好说的,一般使用第一个就好,构造一个QFuture对象,然后用该对象接收QtConcurrent::run() 或 QtConcurrent::mappedReduced()之类的函数的返回值即可。
但我们要记住,当QFuture对象析构时,既不会等待也不会取消异步计算。我们应该使用waitForFinished() 或者 QFutureSynchronizer类还确保在该对象析构之前所有的异步计算都完成。


void QFuture::cancel()
取消该对象代表的异步计算。但请注意,该取消动作也是异步的。如果想同步等待取消完成,要在调用cancel() 函数后,调用一下waitForFinished() 函数。
被取消的future对象,我们仍然可以从中提取到已经可用的结果,但是,在调用cancel() 之后,不会再有新的结果变的可用,而该future对象上的所有QFutureWatcher对象都不会在向我们传送进度信息和结果可用的信号。

再次重申,不是所有的异步计算都可以取消。参看上面已经说过的。


bool QFuture::isCanceled() const
判断异步计算是否被cancel() 函数取消了,但要注意,我们在上面讲cancel() 函数的时候就说过,cancel动作是异步,也就是说当这个函数返回true的时候,异步计算可能还在运行。
void QFuture::pause()
暂停当前future对象代表的异步计算。等价于setPaused(true)
bool QFuture::isPaused() const
判断一个异步计算是否被pause() 函数暂停了。同样,暂停动作也是异步的。

至于其他的成员方法,也都非常容易理解,见名知意,大家在使用时,具体参考Qt帮助文档即可。
上面我们说到,QFuture在取得异步结果方面除了提供了相关的result() 函数,还提供了方便的迭代器类,既有STL风格的QFuture::const_iterator,也有Java风格的QFutureIterator类。其中,STL风格的迭代器和STL中容器的迭代器一样,重载了*和->运算符,以及其他常用的++,--等运算符。而Java风格的迭代器则提供了hasNext()、next()等函数。下面我们分别来看看。

STL风格的迭代器,即QFuture::const_iterator,我们可以定义一个该类型的变量,然后使用QFuture::constBegin() 进行初始化。如下代码所示:

QFuture<QString> future = ...;

QFuture<QString>::const_iterator i;
for (i = future.constBegin(); i != future.constEnd(); ++i)
cout << *i << endl;
同样,对于Java风格的迭代器来说,由于其是一个c++类,所以其构造函数接受一个要进行迭代的QFuture对象,然后使用next() 函数进行逐个遍历。例如以下代码所示:

QFuture<QString> future;
...
QFutureIterator<QString> i(future);
while (i.hasNext())
qDebug() << i.next();
除了next()方法外,QFutureIterator类还提供了previous() 函数,可以完成从后想起的反向遍历。如下代码所示:

QFutureIterator<QString> i(future);
i.toBack();
while (i.hasPrevious())
qDebug() << i.previous();

下面,我们就来写一个使用QFuture的简单例子。因为我们还没讲到QtConcurrent框架及其API,所以,我们就简单的用一下QtConcurrent::run()方法,来启动一个函数,在此函数中计算第100个斐波那契数。代码如下:

#include <QCoreApplication>
#include <QFuture>
#include <QtConcurrent>
#include <QDebug>

//计算第lindex 个 斐波那契数值
qulonglong Fibonacci(int index)
{
qulonglong f1 = 1, f2 = 1, cur = 0;
for(int i = 3; i <= index; i++)
{
cur = f1 + f2;
f1 = f2;
f2 = cur;
}
return cur;
}

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

QFuture<qulonglong> future = QtConcurrent::run(Fibonacci, 100);
//future.waitForFinished();
qDebug() << "test";
qDebug() << future.result();

return a.exec();
}

在此,我们通过使用run()方法,就启动了一个异步计算,紧接着我们调用result() 函数,取得该异步计算的结果。如果此时该结果还不可用,那么调用该函数会是函数阻塞等待结果。当然,我们也可以先调用waitForFinished() 等待其运行结束。其输出结果如下:


先打印出 “test” 字符串,可见,我们通过run() 启动的Fibonacci() 函数确实是异步执行的,并没有阻塞我们的main函数的执行流程。

另外,QtConcurrent在Qt中是一个独立的命名空间,所以还要在.pro文件中引入该模块。如下:

QT += core concurrent


至于QtConcurrent中的其他函数及其相关类,会在后续博文中讲解。

再上面的例子中,我们只是使用QtConcurrent::run()开启了一个异步计算,然后调用QFuture::result() 等待计算的结果。但其实同一时间,我们可以同时开启多个并行运行的异步计算,然后分别使用一个QFuture对象等待结果。但其实在Qt中,为了简化多个QFuture的同步等待操作,特地为我们提供了一个模板类QFutureSynchronizer。该类为我们提供了 addFuture()和setFuture()函数,我们可以使用这两个函数,将代表多个异步计算的QFuture对象添加到一个QFutureSynchronizer对象中,然后调用该类的waitForFinished()方法,该方法就是用来等待所有的QFuture结束。

简单使用例子如下:

void someFunction()
{
QFutureSynchronizer<void> synchronizer;
synchronizer.addFuture(QtConcurrent::run(anotherFunction));
synchronizer.addFuture(QtConcurrent::map(list, mapFunction));

return; // 等待所有的异步计算结束才返回
}
除了等待函数外,QFutureSynchronizer类还提供了一setCancelOnWait()方法,若通过这个函数设置了cancel-on-wait标志,那么调用waitForFinished()函数会取消所有未完成的计算。
下面,我们在上面求斐波那契数的例子中,再开启一个异步计算,同时求出两个斐波那契数

#include <QCoreApplication>
#include <QFuture>
#include <QFutureSynchronizer>
#include <QtConcurrent>
#include <QDebug>

//计算第lindex 个 斐波那契数值
qulonglong Fibonacci(int index)
{
qulonglong f1 = 1, f2 = 1, cur = 0;
for(int i = 3; i <= index; i++)
{
cur = f1 + f2;
f1 = f2;
f2 = cur;
}
return cur;
}

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

QFutureSynchronizer<qulonglong> synchronizer;
synchronizer.addFuture(QtConcurrent::run(Fibonacci, 50));
synchronizer.addFuture(QtConcurrent::run(Fibonacci, 100));
synchronizer.waitForFinished();
qDebug() << "第50个斐波那契数: " << synchronizer.futures()[0].result();
qDebug() << "第100个斐波那契数: " << synchronizer.futures()[1].result();

return a.exec();
}

运行结果如下:

---------------------
作者:求道玉
来源:CSDN
原文:https://blog.csdn.net/Amnes1a/article/details/65630701
版权声明:本文为博主原创文章,转载请附上博文链接!

原文地址:https://www.cnblogs.com/findumars/p/10247584.html