Qt中的 Concurrent 模塊,爲我們提供高級的(high-level)API 編寫多線程程序,而不用使用低級的(low-level)線程元語(如互斥鎖、讀寫鎖、信號量、條件變量等)。
使用的時候需要在.pro文件中添加 concurrent 模塊
QT += concurrent
我們首先來介紹一下 QtConcurrent 中最簡單的使用方法 run() 。
1. run()函數
Concurrent::run() 表示在一個單獨的線程中執行函數。
它的基本原型如下:
QFuture QtConcurrent::run(QThreadPool *pool, Function function, …)
- 參數 function : 表示要在線程中執行的函數。
- 參數 pool :線程池。表示從線程池中獲取一個線程來執行該函數。
- 注意 :函數可能不會立即執行;一旦線程池中的線程有可用的線程時,纔會被執行。
- 返回值 :返回一個 QFuture<T> 對象。後面會詳細說明。
關於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 的值,並不會對計算產生影響。
- 可以爲普通函數、類的成員函數、仿函數、lambda表達式。
調用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
});
...
2. QFuture
QFuture 用來表示異步計算的結果。 QFuture 可以表示一段時間之後的異步計算的結果,使用它可以獲取和控制當前計算的狀態並獲取計算完畢之後的結果。
下面是一個簡單的例子:
extern int func(void);
int func(void) {
// do some thing
}
QFuture<int> future = QtConcurrent::run(func);
int result = future.result(); // 該函數會阻塞等待線程執行func的計算結果返回
QFuture 常用的函數有:
- 獲取結果相關函數: result() 。該函數會判斷結果是否爲可用的,如果不可用則阻塞等待。如果可用則直接把結果返回。
- 設置運行狀態函數: cancel() 表示取消,pause() 暫停,Resumes() 恢復,這些函數在 run() 這種模式下無效。
- 獲取運行狀態函數: isCanceled() 、 isStarted() 、 isFinished() 、 isRunning() 和 isPaused() 。
- 進度信息: progressValue() 、progressMinimum() 、progressMaximum() 和 progressText() ,在 run() 模式下,這些值並沒有正真的意義。waitForFinished() 會阻塞等待計算的完成,直到結果爲可用的狀態。
這裏要注意的是,QFuture<T> 模板 T 這個類型,需要有默認構造和拷貝構造。同時 QFuture 是一個基於引用計數的輕量級的類。
我們通常想要通過信號和槽的函數,異步的監控這些狀態信息。這就需要 QFutureWatcher 這個類。
3. 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);
}
作者: douzhq
個人博客主頁: 不會飛的紙飛機
微信公衆號: 不會飛的紙飛機 ,不定時更新技術文章和搞笑段子。