天天看點

Qt中的多線程及其應用(4)九、另一種建立線程的方式

九、另一種建立線程的方式

面向對象程式設計實踐的早期,工程中習慣于通過繼承的方式擴充系統的功能。

現代軟體架構技術,參考标準,一是盡量使用組合的方式實作系統功能,二是代碼中僅展現需求中的繼承關系。

通過繼承的方式實作新的線程類有什麼實際意義?

Qt中的多線程及其應用(4)九、另一種建立線程的方式

線程的各個子類僅保護的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();
}
           

運作效果如下:

Qt中的多線程及其應用(4)九、另一種建立線程的方式

意義:将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)

子線程通過發射信号的方式更新界面元件,而所有的界面元件對象隻能依附于主線程。

Qt中的多線程及其應用(4)九、另一種建立線程的方式

示例代碼:

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");}
           

子線程能夠改變界面元件狀态的本質是什麼?

Qt中的多線程及其應用(4)九、另一種建立線程的方式

子線程發射信号通知主線程界面請求更新,主線程根據具體信号以及信号參數對界面元件進行修改。

是否有其他間接的方式,可以讓子線程更新界面元件的狀态?

解決方案——發送自定義事件

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()函數,得到目前線程類對象的父元件——主視窗。發送的事件對象必須在堆上建立。

繼續閱讀