九、另一種建立線程的方式
面向對象程式設計實踐的早期,工程中習慣于通過繼承的方式擴充系統的功能。
現代軟體架構技術,參考标準,一是盡量使用組合的方式實作系統功能,二是代碼中僅展現需求中的繼承關系。
通過繼承的方式實作新的線程類有什麼實際意義?
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CXx0ERPRzZ61ENJpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TO2UDMxYjM5ETMzMDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
線程的各個子類僅保護的void run()函數不同,接口部分完全相同。
結論:
通過繼承的方式實作多線程沒有任何實際意義,QThread對應于作業系統中的線程,QThread類用于充當一個線程操作的集合。是以應該提供靈活的方式指定線程的入口參數(槽函數),盡量避免重寫void run()。
靈活指定一個線程對象的線程入口函數——信号與槽
1、在類中定義一個槽函數void tmain()作為線程入口函數;
2、在類中定義一個QThread成員對象m_thread;
3、改變目前對象的線程依附性到m_thread;
4、連接配接m_thread的start()信号到tmain();
AnotherThread::AnotherThread(QObject *parent) : QObject(parent)
{
moveToThread(&m_thread); //改變目前對象的線程依附性
connect(&m_thread, SIGNAL(started()), this, SLOT(tmain()));
}
void AnotherThread::tmain()
{
qDebug() << "void AnotherThread::tmain() tid = " << QThread::currentThreadId();
for(int i=0; i<10; i++)
{
qDebug() << "void AnotherThread::tmain() i = " << i;
}
qDebug() << "void AnotherThread::tmain() end";
}
void AnotherThread::start()
{
qDebug() << " void AnotherThread::start() ";
m_thread.start();
}
void AnotherThread::terminate()
{
m_thread.terminate();
}
void AnotherThread::exit()
{
m_thread.exit();
}
AnotherThread::~AnotherThread()
{
m_thread.wait();
}
void test()
{
AnotherThread at;
at.start();
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << " main() tit" << QThread::currentThreadId();
test();
return a.exec();
}
運作效果如下:
意義:将QThread作為一個線程的操作集,通過信号與槽的方式指定線程入口函數,避免重新run()函數。
十、多線程與界面元件的通信
是否可以在子線程中建立界面元件?
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
TestThread::TestThread(QObject *parent) : QThread(parent)
{
}
void TestThread::run()
{
/* It Is ERROR to create GUI elements in SUB THREAD */
QWidget w;
w.show();
exec();
}
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
TestThread* ptt = new TestThread();
ptt->start(); }
上面代碼視圖在子線程中建立一個視窗,運作上面代碼,程式崩潰,提示資訊為:
ASSERT failure in QWidget: "Widgets must be created in the GUI thread.", file kernel/qwidget.cpp, line 1301
為什麼呢?
GUI系統設計原則:所有界面元件的操作隻能在主線程中完成,是以,主線程也叫作UI線程。
那麼子線程如何對界面元件進行更新呢?——信号與槽
1、在子線程類中定義及诶滿足建的更新信号(updateUI);
2、在主視窗類中定義更新界面元件的槽函數(setInfo);
3、使用異步方式連接配接更新信号到槽函數(updateUI --> setInfo)
子線程通過發射信号的方式更新界面元件,而所有的界面元件對象隻能依附于主線程。
示例代碼:
QApplication a(argc, argv);
Widget w;
w.show();
/******************************************/
UpdateThread m_thread;
QPlainTextEdit textEdit;
Widget::Widget(QWidget *parent) : QWidget(parent){ //TestThread* ptt = new TestThread(); //ptt->start(); textEdit.setParent(this); textEdit.move(20, 20); textEdit.resize(200, 150); textEdit.setReadOnly(true); connect(&m_thread, SIGNAL(updateUI(QString)), this, SLOT(appendText(QString))); m_thread.start();}void Widget::appendText(QString text){ textEdit.appendPlainText(text);}signals: void updateUI(QString text);void UpdateThread::run(){ emit updateUI("Begin"); for(int i=0; i<10; i++) { emit updateUI(QString::number(i)); sleep(1); } emit updateUI("End");}
重點:界面元件的對象必須依附于主線程,進行信号與槽的連接配接必須異步連接配接(同步調用在槽函數傳回時間短,信号等待時間長的情況下也可以,但不推薦)的方式。
Widget::Widget(QWidget *parent) : QWidget(parent){ //TestThread* ptt = new TestThread(); //ptt->start(); textEdit.setParent(this); textEdit.move(20, 20); textEdit.resize(200, 150); textEdit.setReadOnly(true); connect(&m_thread, SIGNAL(updateUI(QString)), this, SLOT(appendText(QString))); m_thread.start();}void Widget::appendText(QString text){ textEdit.appendPlainText(text);}/******************************************/signals: void updateUI(QString text);void UpdateThread::run(){ emit updateUI("Begin"); for(int i=0; i<10; i++) { emit updateUI(QString::number(i)); sleep(1); } emit updateUI("End");}
子線程能夠改變界面元件狀态的本質是什麼?
子線程發射信号通知主線程界面請求更新,主線程根據具體信号以及信号參數對界面元件進行修改。
是否有其他間接的方式,可以讓子線程更新界面元件的狀态?
解決方案——發送自定義事件
1、用自定義事件類用于描述界面更新細節;
2、在主視窗類中重寫事件處理函數event();
3、使用postEvent()函數(異步方式)發送自定義事件類對象:
子線程指定消息的對象為主視窗對象,在event事件處理函數中更新界面狀态。
Qt中的多線程及其應用(4)九、另一種建立線程的方式
代碼示例如下:
QApplication a(argc, argv);
Widget w;
w.show();
/**************************************************/
UpdateThread m_thread;
QPlainTextEdit textEdit;
bool event(QEvent *);//重寫event()函數
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
textEdit.setParent(this);
textEdit.move(20, 20);
textEdit.resize(200, 150);
textEdit.setReadOnly(true);
m_thread.setParent(this); //設定父元件,即此類對象包含父元件資訊
m_thread.start();
}
bool Widget::event(QEvent *evt)
{
bool ret = true;
if( evt->type() == StringEvent::TYPE )
{
StringEvent* se = dynamic_cast<StringEvent*>(evt);
if( se != NULL )
{
textEdit.appendPlainText(se->data());
}
}
else
{
ret = QWidget::event(evt);
}
return ret;
}
/**************************************************/
QString m_data; //描述界面元件的狀态變化
public:
const static Type TYPE = static_cast<Type>(QEvent::User + 0xFF);
explicit StringEvent(QString data);
QString data();
StringEvent::StringEvent(QString data) : QEvent(TYPE)
{
m_data = data;
}
QString StringEvent::data()
{
return m_data;
}
/**************************************************/
void UpdateThread::run()
{
//emit updateUI("Begin");
QApplication::postEvent(parent(), new StringEvent("Begin")); //知道了父元件,向父元件發送一個事件
for(int i=0; i<10; i++)
{
QApplication::postEvent(parent(), new StringEvent(QString::number(i)));
sleep(1);
}
QApplication::postEvent(parent(), new StringEvent("end"));
}
重點:通過重寫event()函數,将
QApplication::postEvent(parent(), new StringEvent("Begin"));
函數中發送的資訊進行類型對比後,輸出至界面顯示。
注意:子線程建立時,必須附帶目标對象的位址資訊,如postEvent()中的parent()函數,得到目前線程類對象的父元件——主視窗。發送的事件對象必須在堆上建立。