Mechanismus tedy spočívá v tom, že nemůžete upravovat widgety zevnitř vlákna, jinak aplikace spadne s chybami jako:
QObject::connect: Cannot queue arguments of type 'QTextBlock'
(Make sure 'QTextBlock' is registered using qRegisterMetaType().)
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
Segmentation fault
Chcete-li to obejít, musíte zapouzdřit práci s vlákny ve třídě, například:
class RunThread:public QThread{
Q_OBJECT
public:
void run();
signals:
void resultReady(QString Input);
};
Kde run() obsahuje veškerou práci, kterou chcete udělat.
Ve své nadřazené třídě budete mít funkci volání generující data a funkci aktualizace widgetu QT:
class DevTab:public QWidget{
public:
void ThreadedRunCommand();
void DisplayData(QString Input);
...
}
Pak pro zavolání do vlákna připojíte nějaké sloty, toto
void DevTab::ThreadedRunCommand(){
RunThread *workerThread = new RunThread();
connect(workerThread, &RunThread::resultReady, this, &DevTab::UpdateScreen);
connect(workerThread, &RunThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
}
Funkce připojení má 4 parametry, parametr 1 je třída příčiny, parametr 2 je signál v této třídě. Parametr 3 je třída funkce zpětného volání, parametr 4 je funkce zpětného volání v rámci třídy.
Pak byste měli ve svém podřízeném vláknu funkci pro generování dat:
void RunThread::run(){
QString Output="Hello world";
while(1){
emit resultReady(Output);
sleep(5);
}
}
Pak byste měli ve své rodičovské funkci zpětné volání k aktualizaci widgetu:
void DevTab::UpdateScreen(QString Input){
DevTab::OutputLogs->append(Input);
}
Když jej pak spustíte, widget v nadřazeném prvku se aktualizuje pokaždé, když je ve vlákně zavoláno makro emit. Pokud jsou funkce připojení správně nakonfigurovány, automaticky převezme vyslaný parametr a uloží jej do vstupního parametru vaší funkce zpětného volání.
Jak to funguje:
- Inicializujeme třídu
- Nastavujeme sloty tak, aby zvládly, co se stane s dokončením vlákna a co dělat s „vráceným“ neboli
emit
ted data, protože nemůžeme vrátit data z vlákna běžným způsobem - pak spustíme vlákno s
->start()
volání (které je pevně zakódováno do QThread) a QT hledá pevně zakódovaný název.run()
členská funkce ve třídě - Pokaždé
emit
makro resultReady je voláno v podřízeném vláknu, ukládá data QString do nějaké sdílené datové oblasti uvízlé v limbu mezi vlákny - QT detekuje, že se výsledek resultReady spustil, a signalizuje vaší funkci UpdateScreen(QString), aby přijala QString vysílaný z run() jako skutečný parametr funkce v nadřazeném vláknu.
- To se opakuje při každém spuštění klíčového slova pro vysílání.
V podstatě connect()
funkce jsou rozhraním mezi podřízeným a nadřazeným vláknem, takže data mohou cestovat tam a zpět.
Poznámka: resultReady() není nutné definovat. Představte si to jako makro existující uvnitř QT internals.
Důležité u Qt je, že musíte pracovat s Qt GUI pouze z GUI vlákna, to je hlavní vlákno.
Správný způsob, jak to udělat, je proto upozornit hlavní vlákno od pracovníka a kód v hlavním vlákně ve skutečnosti aktualizuje textové pole, ukazatel průběhu nebo něco jiného.
Myslím, že nejlepší způsob, jak toho dosáhnout, je použít QThread místo vlákna posix a použít Qt signály pro komunikaci mezi vlákny. Toto bude váš pracovník, náhradník za thread_func
:
class WorkerThread : public QThread {
void run() {
while(1) {
// ... hard work
// Now want to notify main thread:
emit progressChanged("Some info");
}
}
// Define signal:
signals:
void progressChanged(QString info);
};
Ve svém widgetu definujte slot se stejným prototypem jako signál v .h:
class MyWidget : public QWidget {
// Your gui code
// Define slot:
public slots:
void onProgressChanged(QString info);
};
V .cpp implementujte tuto funkci:
void MyWidget::onProgressChanged(QString info) {
// Processing code
textBox->setText("Latest info: " + info);
}
Nyní na místě, kde chcete vytvořit vlákno (po kliknutí na tlačítko):
void MyWidget::startWorkInAThread() {
// Create an instance of your woker
WorkerThread *workerThread = new WorkerThread;
// Connect our signal and slot
connect(workerThread, SIGNAL(progressChanged(QString)),
SLOT(onProgressChanged(QString)));
// Setup callback for cleanup when it finishes
connect(workerThread, SIGNAL(finished()),
workerThread, SLOT(deleteLater()));
// Run, Forest, run!
workerThread->start(); // This invokes WorkerThread::run in a new thread
}
Po připojení signálu a slotu se vysílá slot s emit progressChanged(...)
v pracovním vláknu odešle zprávu do hlavního vlákna a hlavní vlákno zavolá slot, který je připojen k tomuto signálu, onProgressChanged
zde.
P.s. Kód jsem ještě netestoval, takže pokud se někde mýlím, klidně navrhněte úpravu