項目中有一個需求就是,因為需要請求服務端數據,因為網絡的讀取會阻塞,所以該過程不能放在Qt中的UI主線程當中,需要用一個后台線程來讀取數據,數據准備完畢后
在通過Qt5中的信號槽機制來跨線程的傳遞數據。之前的博文使用過moveToThread的方式來講解創建后台線程,但是現在后台線程需要與前台UI線程數據互交,然而,最悲劇的就是信號發出去了,
但是前台的UI線程對象收不到信號,也就是相應的槽函數沒被調用。之前博文后台線程是沒有與前台UI線程互交的,因為它是采集數據的線程,只管往目標地址發送數據就可以了。但是接收線程就不一樣了,
它需要把后台接收到的網絡數據放到前台GUI中展現出來。這不可避免的產生互交和數據的傳遞。
前台的UI線程創建后台線程的代碼大概如下:
1 RecvDataObject *recv_obj = new RecvDataObject; 2 3 QThread* backgroundRecvThread = new QThread; 4 5 6 recv_obj->moveToThread(backgroundRecvThread); 7 8 connect(recv_obj, &RecvDataObject::dataRecved, 9 this, &TerminalStatusWidget::slotDataRecved,Qt::QueuedConnection); 10 11 12 backgroundRecvThread->start();
注意,多線程間的信號槽傳遞,在connect的時候需要以Qt::QueuedConnection的方式,不然以Qt::DirectConnection的方式接收者UI線程會很長時間收不到后台線程發出的信號,或者信號直接丟失都是有可能的。參考
http://www.qtcentre.org/threads/17764-emit-qt-signal-is-very-slow-how-it-can-be-optimized
RecvDataObect是用來接收后台數據的對象被move到了backgroundRecvThread線程中去執行了。其聲明是這樣的:
1 class RecvDataObject : public QObject 2 { 3 Q_OBJECT 4 5 public: 6 RecvDataObject(); 7 ~RecvDataObject(); 8 signals: 9 void dataRecved(std::vector<RunTimeInfo> list); 10 public slots: 11 void slotRecvTask(); 12 private: 13 QTimer m_RecvTask; 14 15 };
該類的構造函數我采用了一個Timer來循環執行slotRecvTask()的任務,專門創建網絡連接,接收網絡數據。然后數據接收完畢后,通過發送dataRecved的信號傳遞到UI主線程中的slot函數中,但是不能正常工作。槽函數一直不能調用。
上網查了原因才知道,原來Qt的信號槽函數只默認支持Qt的類型和C++提供的內建的基本類型,比如int double float啥的,根本不支持C++的std::string std::vector 自定義的struct類型。所以需要用Qt提供的Q_DECLARE_METATYPE和
qRegisterMetaType來聲明和注冊自定義的類型和C++的其他類型。 所以以上的C++類RecvDataObject應該變成以下:
1 Q_DECLARE_METATYPE(RunTimeInfo) 2 Q_DECLARE_METATYPE(std::vector<RunTimeInfo>) 3 4 class RecvDataObject : public QObject 5 { 6 Q_OBJECT 7 8 public: 9 RecvDataObject() 10 { 11 qRegisterMetaType<RunTimeInfo>("RunTimeInfo"); 12 qRegisterMetaType<std::vector<RunTimeInfo>>("std::vector<RunTimeInfo>"); 13 14 m_RecvTask.setInterval(5000); 15 connect(&m_RecvTask, SIGNAL(timeout()), this, SLOT(slotRecvTask())); 16 m_RecvTask.start(); 17 } 18 ~RecvDataObject(); 19 signals: 20 void dataRecved(std::vector<RunTimeInfo> list); 21 public slots: 22 void slotRecvTask(); 23 private: 24 QTimer m_RecvTask; 25 26 };
然后主線程的Widget類的構造函數里面還必須加入:
1 qRegisterMetaType<RunTimeInfo>("RunTimeInfo"); 2 qRegisterMetaType<std::vector<RunTimeInfo>>("std::vector<RunTimeInfo>");
這樣信號槽函數才能正確工作,通過信號槽機制跨線程的數據傳遞完成了,完美運行。
references:
https://stackoverflow.com/questions/638251/how-to-emit-cross-thread-signal-in-qt
http://www.qtcentre.org/threads/54409-signal-slot-with-std-string-How
https://stackoverflow.com/questions/14083599/signals-and-slots-passing-data