Qt中的 Concurrent 模块,为我们提供高级的(high-level)API 编写多线程程序,而不用使用低级的(low-level)线程元语(如互斥锁、读写锁、信号量、条件变量等)。
使用的时候需要在.pro文件中添加 concurrent 模块
QT += concurrent
我们首先来介绍一下 QtConcurrent 中最简单的使用方法 run() 。
它的基本原型如下:
QFuture QtConcurrent::run(QThreadPool *pool, Function function, …)
关于Qt中线程池的使用,可以参考:Qt中的线程池QThreadPool
而我们更为常用的函数是下面的这个:
QFuture QtConcurrent::run(Function function, …)
它等价于:
QtConcurrent::run(QThreadPool::globalInstance(), function, …);
表示从全局的线程池中,获取线程。
关于 function 的说明:
extern void aFunctionWithArguments(int arg1, double arg2, const QString &string);
int integer = ...;
double floatingPoint = ...;
QString string = ...;
QFuture<void> future = QtConcurrent::run(aFunctionWithArguments, integer, floatingPoint, string);
在调用 run() 的时候,这些值会被传递给执行函数,之后修改这些变量的值, 比如重新设置 integer 的值,并不会对计算产生影响。
调用const的成员函数
// call 'QList<QByteArray> QByteArray::split(char sep) const' in a separate thread
QByteArray bytearray = "hello world";
QFuture<QList<QByteArray> > future = QtConcurrent::run(bytearray, &QByteArray::split, ',');
...
QList<QByteArray> result = future.result();
调用non-const成员函数
// call 'void QImage::invertPixels(InvertMode mode)' in a separate thread
QImage image = ...;
QFuture<void> future = QtConcurrent::run(&image, &QImage::invertPixels, QImage::InvertRgba);
...
future.waitForFinished();
// At this point, the pixels in 'image' have been inverted
使用lambda表达式:
QFuture<void> future = QtConcurrent::run([=]() {
// Code in this block will run in another thread
});
...
QFuture 用来表示异步计算的结果。 QFuture 可以表示一段时间之后的异步计算的结果,使用它可以获取和控制当前计算的状态并获取计算完毕之后的结果。
下面是一个简单的例子:
extern int func(void);
int func(void) {
// do some thing
}
QFuture<int> future = QtConcurrent::run(func);
int result = future.result(); // 该函数会阻塞等待线程执行func的计算结果返回
QFuture 常用的函数有:
这里要注意的是,QFuture<T>模板 T 这个类型,需要有默认构造和拷贝构造。
同时 QFuture 是一个基于引用计数的轻量级的类。
我们通常想要通过信号和槽的函数,异步的监控这些状态信息。这就需要 QFutureWatcher 这个类。
QFutureWatcher 使我们通过信号和槽的方式,监控 QFuture 对象。
下面是一个示例:
// Instantiate the objects and connect to the finished signal.
MyClass myObject;
QFutureWatcher<int> watcher;
connect(&watcher, SIGNAL(finished()), &myObject, SLOT(handleFinished()));
// Start the computation.
QFuture<int> future = QtConcurrent::run(...);
watcher.setFuture(future);
函数 setFuture 表示设置被监控的 QFuture 对象,
为了避免我们设置后不能及时的收到 QFuture 对象发送的信号,可以在执行 run 之前设置信号和槽的连接。
当 QFuture 对象的结果可用的时候, finished
信号会被 QFutureWatcher 发送。
下面是一个关于 run 使用的简单示例,在单独的线程中计算数值,异步通知GUI线程,这样GUI线程就不用阻塞等待计算结果的完成。效果如下:
这是一个计算前N个数和的示例,开启单独线程异步计算。
为了防止计算的太快,我这里故意在计算函数中加了延时:
double calcTotalNumber(int number)
{
double sum = 0.0;
for (int i=0; i<=number; ++i)
{
sum += i;
QThread::msleep(10);
}
return sum;
}
完整代码如下:
头文件:
class QtConcurrentTestWidget : public CustomWidget
{
Q_OBJECT
public:
QtConcurrentTestWidget(QWidget *parent = nullptr);
~QtConcurrentTestWidget();
private:
QLabel* m_pResultLabel = nullptr;
QSpinBox* m_pSpinBox = nullptr;
QPushButton* m_pButton = nullptr;
QFuture<double> m_future;
QFutureWatcher<double> m_futureWatcher;
private slots:
void onCalcButtonClicked(void);
void threadStarted(void);
void threadFinished(void);
};
cpp文件:
QtConcurrentTestWidget::QtConcurrentTestWidget(QWidget *parent)
: CustomWidget(parent)
{
// 创建界面
QVBoxLayout* layout = new QVBoxLayout(this);
// 显示结果Label
m_pResultLabel = new QLabel();
layout->addWidget(m_pResultLabel);
QHBoxLayout* bottomLayout = new QHBoxLayout;
// 创建 SpinBox
m_pSpinBox = new UICustomIntSpinBox;
m_pSpinBox->setMinimum(1);
m_pSpinBox->setMaximum(999999);
m_pSpinBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
bottomLayout->addWidget(m_pSpinBox);
// 添加按钮
m_pButton = new QPushButton(tr("Calc"));
m_pButton->setMinimumSize(60, 30);
bottomLayout->addWidget(m_pButton);
layout->addLayout(bottomLayout);
QObject::connect(m_pButton, &QPushButton::clicked, this, &QtConcurrentTestWidget::onCalcButtonClicked);
// 连接监视器的信号
QObject::connect(&m_futureWatcher, &QFutureWatcher<double>::started, this, &QtConcurrentTestWidget::threadStarted);
QObject::connect(&m_futureWatcher, &QFutureWatcher<double>::finished, this, &QtConcurrentTestWidget::threadFinished);
}
QtConcurrentTestWidget::~QtConcurrentTestWidget()
{
}
// 点击计算按钮
void QtConcurrentTestWidget::onCalcButtonClicked(void)
{
m_pButton->setEnabled(false);
m_future = QtConcurrent::run(calcTotalNumber, m_pSpinBox->value());
m_futureWatcher.setFuture(m_future);
}
// 开始执行
void QtConcurrentTestWidget::threadStarted(void)
{
m_pResultLabel->setText("Thread Started!");
}
// 执行完成
void QtConcurrentTestWidget::threadFinished(void)
{
// 获取结果
double result = m_future.result();
// 显示结果
QString str = "Calc Result is %1";
str = str.arg(result);
m_pResultLabel->setText(str);
m_pButton->setEnabled(true);
}