關於QT的線程通信,我們都會想到signal & slot機制。先回顧下利用signal & slot機制實現控件消息處理的方法。
控件消息處理
假設我們的主界面上有一個使用ui->btn指向的QPushButton對象,要實現該對象的clicked消息處理,可以在主界面對象MainWindow上添加一個slot方法onBtnClicked,並在其構造函數中使用connect方法與ui->btn的clicked消息進行綁定,如下:
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new ui::MainWindow) { ui->setupUi(this); // 方法一,推薦使用 connect(ui->btn, &QPushButton::clicked,
this, &MainWindow::onBtnClicked); // 方法二 //connect(ui->btn, SIGNAL(clicked),
this, SLOT(onBtnClicked)); … }
void MainWindow::onBtnClicked(void)
{
doSomething();
}
事實上這種方法很繁雜。單純為了處理一個按鈕的消息我們就要新建一個slot方法,當界面控件多了的時候,會使得代碼臃腫不堪(即使你可以設計成多個控件消息共享一個槽函數)。
在新版的支持C++11的QT中,我們可以使用Lambda函數優雅地解決這個問題。如下:
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new ui::MainWindow) { setupUi(this); // 使用lambda函數實現slot方法 connect(ui->btn, &QPushButton::clicked, [&](){ doSomething(); }); … }
使用Lambda函數的另一個好處是,可以借助閉包的概念加速開發。比如我曾做過一個運動控制項目,界面上有32個之多的QCheckBox控件,如下:
需要響應每個QCheckBox控件的clicked消息,改變對應的IO口電平輸出。我的做法是,對這些控件進行Tab排序,並將第一個控件命名為ui->ioChk,然后編碼如下:
typedef QCheckBox *F_QCB; #define F_QNEXT(w) (w->nextInFocusChain())
…
MainWindow::MainWindow(QWidget* parent) :
QMainWindow(parent), ui(new ui::MainWindow)
{
setupUi(ui);
…
F_QCB cb = ui->ioChk;
for (int i = 0; i < 32; ++i)
{
connect(cb, &QCheckBox::clicked, [=](bool s){
setIo(i, s); // 改變第i個端口的輸出電平為s
});
cb = F_QNEXT(cb);
}
…
}
要讀懂上面的程序,你需要一些關於C++11標准的Lambda函數的知識,代碼中使用值捕獲方式捕獲i變量值形成閉包函數,這些閉包函數被綁定到不同的QCheckBox的clicked消息中。
線程間通信
很長的時間里,我都以為QT的signal & slot機制只適用於單向異步通信。事實上,它可以設計為帶返回的同步通信。
有個案例如下:工作線程在運行時需要同步提取界面上的參數信息(假設要提取界面上的QSpinBox的值),要同時保證界面的響應和工作線程的不間斷運轉。
一種可能的實現方法是,在工作線程上的某個對象定義一個request消息,綁定到MainWindow的response槽中,然后在MainWindow中定義一個answer消息,綁定到工作線程上的某個對象的onAnswer槽中。通信過程如下:
1. 工作線程需要界面參數值,發送request消息
2. 主線程接收到request消息,開始執行response槽方法
3. 在response函數中,獲取界面控件的值,發送answer消息
4. 工作線程收到answer消息,調用onAnswer槽方法,恢復之前的運行流程
這種方法條理清晰,但編碼實現過於繁瑣。我的實現方法是,在工作線程實現一個形如request(QString req, QVariant& arg)的消息,然后以QT::BlockBlockingQueuedConnection方式連接至MainWindow的response槽方法中。編碼如下:
WorkerThread聲明:
#ifndef __WORKER_THREAD_H
#define __WORKER_THREAD_H
class WorkerThread : public QThread {
public:
…
// @Override
void run();
private:
// 用於通信
class InThreadObject;
InThreadObject *ito;
…
}
#endif
WorkerThread實現:
class WorkerThread::InThreadObject : public QObject {
Q_OBJECT
signals:
void request(QString req, QVariant& arg);
}
void WorkerThread::run()
{
ito = new InThreadObject;
…
// 注意下面的連接使用了QT::BlockingQueuedConnection選項
connect(ito,
&InThreadObject::request,
pMainWindow,
&MainWindow::response,
QT::BlockingQueuedConnection);
…
// 同步獲取界面參數值
QVariant var;
ito->request("param.level", var);
// 這里會同步等待主線程執行response函數完
int param = var.toInt();
……
}
MainWindow相關實現:
void MainWindow::response(QString req, QVariant& ans)
{
if (req == "param.level")
{
// 獲取控件值並存放至ans引用變量中
ans.setValue(ui->levelSpin->value());
}
}
本次經驗分享完畢,關於一些具體的技術細節,如QT的消息與槽機制、C++11的Lambda函數還請讀者另行學習。
本文鏈接:http://www.cnblogs.com/wenris/p/4447481.html。
作者:wenris,聯系:<wenris AT yeah.net>
后會有期哦 O(∩_∩)O