Post

Zwei Methoden, um einen Qt Workerthread zu verwenden

Zwei Methoden, um einen Qt Workerthread zu verwenden

In diesem Artikel wird zwei unterschiedliche Ansätze beschrieben, wie man einen Workerthread in eine Qt GUI Anwendung integriert und es werden die jeweiligen Vor- und Nachteile der Ansätze gezeigt. Qt bietet mit der Klasse QThread eine Kapselung der plattformspezifischen Threads an. Es gibt aber auch weitere Thread-bezogene Klassen, wie QThreadPool, QRunnable und QFuture. Einen Überblick über die verschiedenen Techniken gibt die Qt Dokumentation: Multithreading Technologies in Qt.

Hier werden die beiden üblichsten low-level API Varianten für die Implementierung eines Workerthreads mit Signal/Slot-Kommunikation zum GUI/Hauptthread gezeigt. Die beiden unterschiedlichen Implementierungen sind als komplette Beispiele herunterladbar:

Workerthread als abgeleitete Klasse

Die naheliegenste Implementierung eines Workerthreads ist das einfache Ableiten der Klasse QThread und re-implementieren der run() Funktion. Das schauen wir uns als erstes an.

Die wichtigsten Bausteine werden nachfolgend vorgestellt.

Hauptprogramm und MainWidget

Minimalistisch.

main.cpp:

1
2
3
4
5
6
7
8
9
#include <QApplication>
#include "MainWidget.h"

int main(int argc, char *argv[]) {
	QApplication a(argc, argv);
	MainWidget w;
	w.show();
	return a.exec();
}

Das Widget selbst hat ein QLabel, einen Start und Stop QPushButton und eine Fortschrittsanzeige QProgressBar.

MainWidget.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>

class WorkerThread;
class QPushButton;
class QProgressBar;

/*! Simple widget with a label and a button */
class MainWidget : public QWidget {
	Q_OBJECT
public:
	MainWidget(QWidget *parent = nullptr);
	~MainWidget();

private slots:
	/*! Called when start button was pressed - creates and launches the worker thread. */
	void onStartButtonClicked();

	/*! Called from worker thread. Updates progress bar in dialog. */
	void onProgress(int step);

	/*! Called when worker has finished. */
	void onThreadFinished();

private:
	QPushButton		*m_startBtn = nullptr;
	QPushButton		*m_stopBtn = nullptr;
	QProgressBar	*m_progress = nullptr;

	/*! The worker thread. */
	WorkerThread * m_worker = nullptr;
	/*! Some data for the thread to work on read-only. */
	QVector<double>	m_data;
	/*! Result data retrieved from thread when it is done. */
	QVector<double>	m_results;
};

#endif // MAINWIDGET_H

Nun der Blick auf die Implementierung des Widgets. Eigentlich ist nur die Funktion MainWidget::onStartButtonClicked() interessant.

MainWidget::onStartButtonClicked():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void MainWidget::onStartButtonClicked() {
	m_stopBtn->setEnabled(true);
	m_startBtn->setEnabled(false);
	m_progress->setVisible(true);
	m_progress->setValue(0);

	m_worker = new WorkerThread; // create worker thread
	connect(m_worker, &QThread::finished, this, &MainWidget::onThreadFinished);
	connect(m_worker, &WorkerThread::progress, this, &MainWidget::onProgress);
	connect(m_stopBtn, &QPushButton::clicked, m_worker, &WorkerThread::onStopClicked);

	m_data.resize(100000);
	for (int i=0; i<m_data.size(); ++i)
		m_data[i] = i;

	// give worker thread access to our memory (read-only)
	m_worker->m_data = &m_data;

	// start worker thread
	m_worker->start();
}

Damit nicht mehrere Workerthreads gleichzeitig gestartet werden können, werden am Anfang der Funktion die Schaltflächen deaktiviert bzw. aktiviert.

Erstellt wird der Workerthread auf dem Heap und danach wird das Signal QThread::finished() mit der Funktion MainWidget::onThreadFinished() verknüpft. Hier wird nach Ende des Threads wieder aufgeräumt.

Damit der Workerthread Arbeitsdaten hat, wird im mit m_worker->m_data = &m_data; Zugriff auf die Daten des GUI-Threads gegeben (siehe Bemerkungen dazu im nächsten Abschnitt).

Schließlich wird der Workerthread mit m_worker->start(); gestartet. Danach kehrt die Funktion zurück und die EventLoop des GUI-Threads läuft wieder vor sich hin.

Arbeitsdaten an den Workerthread übertragen

Der Workerthread brauch Arbeitsdaten vom GUI Thread. In unserem Beispiel oben wird auf die Daten des GUI-Threads nur lesend zugegriffen, daher übergeben wir nur die Speicheraddresse der Arbeitsdaten.

Während der Workerthread läuft, darf der GUI-Thread diese Daten nicht verändern.

Sollte der GUI Thread diese Daten verändern wollen (bspw. weil der Nutzer Eingaben tätigt), sollte der Workerthread die Daten als Kopie übernehmen. Dabei ist es vielleicht interessant zu wissen, dass die Qt Klassen mit implicit sharing sicher zwischen Threads kopiert werden können und auch bei Multi-Thread-Anwendungen sichergestellt ist, dass das reference-counting korrekt funktioniert. Details dazu hab ich in meinem Post Reference-Counting (implicit-sharing) bei Qt Klassen beschrieben.

Workerthread Implementierung

Die Implementierung des Workerthreads ist eigentlich recht einfach:

WorkerThread.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H

#include <QThread>

class WorkerThread : public QThread {
	Q_OBJECT
public:
	/*! Input data for thread.
		If GUI guarantees, that this memory is untouched while the thread is running, we
		can simply store the address and access the memory without creating a copy.
		If the GUI continues changing the memory while the thread is running, we need to make a copy.
	*/
	const QVector<double> * m_data = nullptr;

	/*! Here we store the results to be accessed by GUI thread once the worker has finished */
	QVector<double>	m_results;

	/*! Will be set to true in onStopClicked() and stops the loop. */
	bool	m_stopRequested = false;

public slots:
	void onStopClicked() { m_stopRequested = true; }

signals:
	/*! Emitted in regular intervals. */
	void progress(int step);

protected:
	/*! Re-implemented QThread::run() function - does all the work. */
	void run() override;
};

#endif // WORKERTHREAD_H

Und die dazugehörige Implementierung:

WorkerThread.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "WorkerThread.h"

void WorkerThread::run() {
	const double * constData = m_data->constData(); // no detach() here!
	m_results.resize(m_data->size());
	for (int i=0; i<1000; ++i) { // loop 10000 times to generate some work
		if (i % 10 == 0) {
			emit progress(i/10);
			if (m_stopRequested)   // if onStopClicked() was called, we stop here
				return;
		}
		// do some (useless) work... sum
		double x = 0;
		for (int j=0; j<m_data->size(); ++j) {
			x += constData[j]*constData[j];
			m_results[j] = x;
		}
	}
	emit progress(100);
}

Threadende und Datenabruf

Wenn der Workerthread fertig ist, sendet der Thread das Signal QThread::finished() aus. Das wird im GUI-Thread empfangen und im Slot verarbeitet.

MainWidget::onThreadFinished():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void MainWidget::onThreadFinished() {
	// check if thread was aborted
	if (m_worker->m_stopRequested) {
		QMessageBox::critical(this, QString(), tr("Thread was aborted"));
	}
	else {
		m_results = m_worker->m_results; // transfer data
		QMessageBox::information(this, QString(), tr("Thread finished"));
	}
	m_stopBtn->setEnabled(false);
	m_startBtn->setEnabled(true);
	m_progress->setVisible(false);
	delete m_worker;    // Thread is no longer running, delete it here
	m_worker = nullptr; // set to nullptr, so we can start another run
}

Da der Thread nun nicht mehr läuft kann man gefahrlos Daten aus dem Threadobjekt zurückkopieren und außerdem das WorkerThread-Objekt löschen.

Normalerweise gilt bei Qt die Regel, dass man Objekte, die ein Signal verschicken, im aufgerufenen Slot nicht löschen darf. Hier wird das Signal QThread::finished() aber von einem anderen Thread gesendet und wird von der EventLoop des empfangenden GUI-Threads abgearbeitet. Daher ist das sendende QThread-Objekt raus aus dem CallStack und kann gefahrlos aufgeräumt werden.

Kommunikation zwischen Workerthread und GUI Thread während der Thread läuft

Im Workerthread läuft keine klassische Eventloop. Trotzdem kommuniziert der Thread mit dem GUI Thread mittels Signalen.

Der Workerthread sendet in regelmäßigen Abständen das Signal progress(int), welches an den GUI-Thread zur Aktualisierung des Fortschrittsbalkens gesendet wird.

Der Slot onStopClicked() wird aufgerufen, wenn der Anwender den Stop-Button in der Oberfläche klickt, und setzt intern lediglich die Variable m_stopRequested = true.

Die Funktion WorkerThread::onStopClicked() ist zwar Teil des Threads, wird aber vom GUI Thread abgearbeitet. Im WorkerThread wurde ja keine eigene EventLoop gestartet. Das ist beim Setzen einer einfachen bool-Variable nicht kritisch, aber wenn größere Datenänderungen erfolgen, sollte man diese Änderung mit einem Mutex absichern. Grundsätzlich sollte man immer prüfen, welcher Thread eine Slot-Funktion eigentlich abarbeitet (z.B. im Debugger, Thread-Ansicht).

In der WorkerThead::run()-Funktion wird regelmäßig geprüft, ob dieses Flag gesetzt wurde und falls ja, wird die Arbeit im Thread direkt abgebrochen.

In regelmäßigen Abständen wird während der Arbeitsschleife der Fortschritt via Signal progress() an die GUI geschickt. Dort wird der Slot MainWidget::onProgress() aufgerufen:

1
2
3
void MainWidget::onProgress(int step) {
	m_progress->setValue(step);
}

Bei Signal-Slot-Verknüpfungen zwischen unterschiedlichen Threads sollte eine Qt::QueuedConnection verwendet werden (siehe auch Erläuterung dazu in meinem Post Sinnige/richtige Implementierung eines Multi-Threaded TCP-Servers mit Qt ). Der Aufruf von emit führt deswegen nicht zur sofortigen Abarbeitung des Signals. Stattdessen wird der Aufruf nur in der EventQueue des anderen Threads als Signal-TODO hinterlegt. Das kann man sehen, wenn man einen Breakpoint in den Slot MainWindow::onProgress() setzt. Die Funktion wird vom GUI Thread abgearbeitet, während der WorkerThread bereits die Berechnungsschleife abarbeitet.

Während der WorkerThread läuft wird so der Fortschrittsbalken aktualisiert und man kann gleichzeitig die GUI flüssig verwenden - also genau das, was wir mit dem WorkerThread bezwecken wollten.

Workerthread-derived-from-QThread-With-Signals-Example

Thread-Abbruch bei Programmende

Wenn man das Programm beendet, während der WorkerThread noch läuft, kann das zu undefiniertem Verhalten führen. Daher sollte man alle Workerthreads im Destruktor des Hauptprogramms (oder besser noch im closeEvent()) beenden.

1
2
3
4
5
6
MainWidget::~MainWidget() {
	if (m_worker != nullptr) {
		m_worker->quit();
		m_worker->wait(1000);
	}
}

Zusammenfassung

Die Variante mit der Ableitung der QThread Klasse und Implementierung der run() Funktion ist sehr einfach umzusetzen. Falls Signal-Slot-Kommunikation mit anderen Threads benötigt wird, muss der WorkerThread regelmäßig Signale emitten.

Es läuft keine EventLoop im Workerthread. Diese Implementierungsvariante ist also nicht geeignet falls man Komponenten verwendet (asynchrone Kommunikation etc.), welche zwingend eine EventLoop voraussetzen.

QThread mit Workerobjekt

Die zweite Variante (nicht mein Favorit) ist auch häufig anzutreffen und ist der obigen sehr ähnlich. Diese Variante hat aber einige Fallstricke und braucht mehr Code. Die Grundidee ist hier, dass man die QThread-Klasse unverändert benutzt, und lediglich eine Arbeitsfunktion von der QThread-Eventloop ausführen lässt.

Die Standardimplementierung von QThread::run() startet einfach die Eventloop durch Aufruf von QThread::exec().

Da der startende Thread selbst das Signal QThread::started() aussendet, knüpft man an dieses Signal die eigene Arbeitsfunktion. Wichtig ist hier zu bedenken, dass solange die eigene Arbeitsfunktion läuft, die EventLoop nicht weiter abgearbeitet wird. Dass muss man wie nachfolgend gezeigt manuell anstoßen.

MainWidget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>

class Worker;
class QPushButton;
class QProgressBar;

/*! Simple widget with a label and a button */
class MainWidget : public QWidget {
	Q_OBJECT
public:
	MainWidget(QWidget *parent = nullptr);
	~MainWidget();

private slots:
	/*! Called when start button was pressed - creates and launches the worker thread. */
	void onStartButtonClicked();

	/*! Called from worker thread. Updates progress bar in dialog. */
	void onProgress(int step);

	/*! Called when worker has finished. */
	void onThreadFinished();

private:
	QPushButton		*m_startBtn = nullptr;
	QPushButton		*m_stopBtn = nullptr;
	QProgressBar	*m_progress = nullptr;

	/*! The worker object. */
	Worker			*m_worker = nullptr;
	/*! The thread. */
	QThread			*m_thread = nullptr;
	/*! Some data for the thread to work on read-only. */
	QVector<double>	m_data;
	/*! Result data retrieved from thread when it is done. */
	QVector<double>	m_results;
};

#endif // MAINWIDGET_H

Bis auf die Trennung zwischen Workerobjekt in der Klasse Worker und dem QThread-Objekt ist die Klasse identisch zum Beispiel oben.

Beim Starten und Stoppen des Threads gibt es aber Unterschiede:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void MainWidget::onStartButtonClicked() {
	m_stopBtn->setEnabled(true);
	m_startBtn->setEnabled(false);
	m_progress->setVisible(true);
	m_progress->setValue(0);

	m_data.resize(100000);
	for (int i=0; i<m_data.size(); ++i)
		m_data[i] = i;

	// create worker
	m_worker = new Worker(); // must not get a parent to move it to thread later

	// give worker thread access to our memory (read-only)
	m_worker->m_data = &m_data;

	// create thread object
	m_thread = new QThread();
	// move worker to thread so that events are processed there
	m_worker->moveToThread(m_thread);
	// let thread execute the worker's run function
	connect( m_thread, &QThread::started, m_worker, &Worker::run);
	connect( m_worker, &Worker::finished, this, &MainWidget::onThreadFinished);
	connect(m_worker, &Worker::progress, this, &MainWidget::onProgress);
	connect(m_stopBtn, &QPushButton::clicked, m_worker, &Worker::onStopClicked);
	// start worker thread
	m_thread->start();
}

Das Worker-Objekt darf nicht in die QObjekt-Hierarchie eingebunden sein, d.h. darf kein parent haben! Das ist wichtig und damit kann man z.B. kein QWidget oder sonstiges in eine Hierarchie eingebettetes Objekt als Worker verwenden.

Nachdem der Worker erstellt wurde, wird der Thread erstellt und der Worker in den Kontekt des Threads geschoben:

1
2
3
4
// create thread object
m_thread = new QThread();
// move worker to thread so that events are processed there
m_worker->moveToThread(m_thread);

Im Vergleich zum ersten Beispiel kommt hier eine weitere Signal-Slot-Verknüpfung dazu. Das started() Signal des Threads wird mit der eigentlichen Arbeitsfunktion verknüpft.

1
connect( m_thread, &QThread::started, m_worker, &Worker::run);

Und zuletzt wird der Thread gestartet.

Das Worker-Objekt

Das Worker-Objekt sieht unserer WorkerThread-Klasse aus dem ersten Beispiel sehr ähnlich.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#ifndef WORKER_H
#define WORKER_H

#include <QObject>

class Worker : public QObject {
	Q_OBJECT
public:
	/*! Input data for thread. */
	const QVector<double> * m_data = nullptr;

	/*! Here we store the results to be accessed by GUI thread only the thread has finished. */
	QVector<double>	m_results;

	/*! Will be set to true in onStopClicked() and stops the loop. */
	bool	m_stopRequested = false;

public slots:
	void onStopClicked() { m_stopRequested = true; }

	/*! Will be executed by thread when it starts - does all the work. */
	void run();

signals:
	/*! Emitted when worker has finished. */
	void finished();
	/*! Emitted in regular intervals. */
	void progress(int step);
};

#endif // WORKER_H

Es gibt zwei kleine Unterschiede im Vergleich zum ersten Beispiel:

  • die eigentliche Arbeitsfunktion run() ist nun ein public slot
  • es gibt ein Signal finished(), mit dem der Worker-Thread sagt “ich bin fertig” (wobei der QThread selbst dann noch läuft, siehe nächster Abschnitt)

Die Implementierung ist auch nur minimal anders:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include "Worker.h"

#include <QCoreApplication>

void Worker::run() {
	const double * constData = m_data->constData(); // no detach() here!
	m_results.resize(m_data->size());
	for (int i=0; i<1000; ++i) { // loop 10000 times to generate some work
		if (i % 10 == 0) {
			emit progress(i/10);
			qApp->processEvents();
			if (m_stopRequested) {  // if onStopClicked() was called, we stop here
				emit finished(); // don't forget to emit the finished signal
				return;
			}
		}
		// do some (useless) work... sum
		double x = 0;
		for (int j=0; j<m_data->size(); ++j) {
			x += constData[j]*constData[j];
			m_results[j] = x;
		}
	}
	emit finished();
}

Wichtig ist es, auch beim vorzeitigen Beenden der Arbeitsfunktion (also wenn m_stopRequested gesetzt ist), das Signal finished() zu senden.

Kommunikation zwischen Threads

Wie oben schon bemerkt wurde, läuft im QThread standardmäßig eine EventLoop. Diese arbeitet die Signale ab, wie z.B. das started() Signal und ruft dabei die Funktion Worker::run() auf. Solange diese Funktion nun aber abgearbeitet wird, verharrt die EventLoop und wartet geduldig darauf, dass es weiter geht.

Im Vergleich zum ersten Beispiel gibt es aber diese EventLoop, und wenn der Nutzer im GUI-Thread die Stopp-Schaltfläche drückt, wird das Slot Worker::onStopClicked() nicht direkt aufgerufen (wie im Beispiel oben), sondern es wird lediglich der Aufruf dieses Slots an die EventQueue des Threads angehängt. Und dort wartet er darauf, dass die EventLoop irgendwann mal weiter macht. Das passiert aber erst, wenn die run()-Funktion verlassen wird, und die Funktionsausführung zur EventLoop zurückkehrt.

Wenn man das Abarbeiten der EventLoop noch während der Laufzeit der run()-Funktion erzwingen will, muss man die Funktion QCoreApplication::processEvents() aufrufen. Diese geht dann die Events der EventQueue durch und ruft letztlich auch den Slot onStopClicked() auf. Man kann das gut im Callstack sehen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1  Worker::onStopClicked                                                           Worker.h           19  0x555555559c06 
2  QtPrivate::FunctorCall<QtPrivate::IndexesList<>, QtPrivate::List<>, void, voi   qobjectdefs_impl.h 152 0x55555555b6c0 
3  QtPrivate::FunctionPointer<void (Worker:: *)()>::call<QtPrivate::List<>, void   qobjectdefs_impl.h 185 0x55555555b4d2 
4  QtPrivate::QSlotObject<void (Worker:: *)(), QtPrivate::List<>, void>::impl(in   qobjectdefs_impl.h 418 0x55555555b293 
5  QObject::event(QEvent *)                                                                               0x7ffff7506343 
6  QApplicationPrivate::notify_helper(QObject *, QEvent *)                                                0x7ffff796bd45 
7  QCoreApplication::notifyInternal2(QObject *, QEvent *)                                                 0x7ffff74d8118 
8  QCoreApplicationPrivate::sendPostedEvents(QObject *, int, QThreadData *)                               0x7ffff74db94b 
9  ??                                                                                                     0x7ffff7535c0f 
10 ??                                                                                                     0x7ffff5d145b5 
11 ??                                                                                                     0x7ffff5d73717 
12 g_main_context_iteration                                                                               0x7ffff5d13a53 
13 QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>)                             0x7ffff7535279 
14 Worker::run                                                                     Worker.cpp         11  0x55555555b8af 
15 QtPrivate::FunctorCall<QtPrivate::IndexesList<>, QtPrivate::List<>, void, voi   qobjectdefs_impl.h 152 0x55555555b6c0 
16 QtPrivate::FunctionPointer<void (Worker:: *)()>::call<QtPrivate::List<>, void   qobjectdefs_impl.h 185 0x55555555b4d2 
17 QtPrivate::QSlotObject<void (Worker:: *)(), QtPrivate::List<>, void>::impl(in   qobjectdefs_impl.h 418 0x55555555b293 
18 ??                                                                                                     0x7ffff7512a76 
19 QThread::started(QThread::QPrivateSignal)                                                              0x7ffff72d940d 
20 ??                                                                                                     0x7ffff72db65d 
21 start_thread                                                                    pthread_create.c   447 0x7ffff6a9caa4 
22 clone3                                                                          clone3.S           78  0x7ffff6b29c3c 

In 19 wird das QThread::started() Signal verarbeitet, ruft dann in 14 die Arbeitsfunktion Worker::run() auf. Diese ruft dann in 13 processEvents() auf, und letztlich beim Abarbeiten der Events wird dann 1 Worker::onStopClicked() aufgerufen.

Um im Worker auf Signale zu reagieren, muss man regelmäßig qApp->processEvents() aufrufen.

Worker- und Threadende

Wenn der Worker fertig ist, sendet er das Signal Worker::finished() aus und dann beendet sich die Funktion run(). Der QThread läuft jedoch munter weiter und ackert die leere EventLoop ab.

Das Signal wird im GUI-Thread abgearbeitet, im Slot MainWidget::onThreadFinished():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void MainWidget::onThreadFinished() {
	// check if thread was aborted
	if (m_worker->m_stopRequested) {
		QMessageBox::critical(this, QString(), tr("Thread was aborted"));
	}
	else {
		m_results = m_worker->m_results; // transfer data
		QMessageBox::information(this, QString(), tr("Thread finished"));
	}
	m_thread->quit(); // tell thread to stop its event loop
	m_thread->wait(); // wait for thread to stop its event loop

	// thread and worker are no longer running, we can delete them here
	delete m_thread;
	delete m_worker;
	m_worker = nullptr; // set to nullptr, so we can start another run
	m_thread = nullptr;

	m_stopBtn->setEnabled(false);
	m_startBtn->setEnabled(true);
	m_progress->setVisible(false);
}

Im Vergleich zum ersten Beispiel hat man hier deutlich mehr zu tun.

  1. Die noch laufende Eventloop im QThread stoppen (m_thread->quit())
  2. Warten, bis der Thread wirklich am Ende ist. Die Funktion wait() hält den GUI Thread solange an, bis der Thread wirklich beendet wurde. ACHTUNG: ein Thread könnte noch beim Abarbeiten eines Slots hängen, daher ist hier je nach Implementierung eine besondere Fehlerbehandlung notwendig.
  3. Speicher beider Objekte aufräumen

In anderen Beispielen dieser QThread mit Worker-Objekt Implementierung findet man häufig Aufrufe m_thread->quit(); m_thread->deleteLater(); oder eine direkte Verknüpfung des Worker::finished()-Signals mit QThread::quit() und QThread::deleteLater(). Das ist zwar an sich nicht falsch, aber eben gefährlich, wenn beim Beenden der EventLoop irgendwas klemmt. Aus meiner Sicht ist ein explizites und kontrolliertes Aufräumen des Threads und Workers in einem Slot die bessere (und auch besser debug-bare) Variante.

Intermittierende Aufrufe der Arbeitsfunktion

In manchen Programmen gibt es nicht die eine Arbeitsfunktion, sondern mehrere Teilaufgaben, die wiederholt ggfs. nach Eintreffen von Daten aus anderen Threads abgearbeitet werden. Dann kann man folgende Strategie anwenden:

1
2
3
4
5
6
7
8
9
Worker::repeatedRun() {
	// do work
	// ...
	
	// register call to our function in EventLoop with minimal delay
	// this allows QThread to process its eventloop before passing control back
	// to us
	QTimer::singleShot(200, this, &Worker::repeatedRun);
}

Hier wird am Ende eines Abarbeitungsschnipsels die Arbeitsfunktion verlassen. Vorher wird aber noch ein Single-Shot Event in die EventQueue gepackt, welche die Arbeitsfunktion dann gleich nochmal aufruft. Zwischenzeitlich kann die EventQueue entspannt abgearbeitet werden. Bei dieser Implementierung (und vorausgesetzt, dass die repeatedRun()-Funktion selbst schnell genug ist), kommt man ohne Aufrufe von processEvents() aus.

Zusammenfassung

Die Variante mit getrenntem Worker-Objekt und QThread erfordert mehr Sorgfalt bei der Umsetzung (und mehr Signale/Slots) als die erste Variante mit der abgeleiteten QThread-Klasse. Dafür kann man eine EventLoop im Thread nutzen, was gerade für asynchrone Kommunikation häufig notwendig ist. Hierbei muss man aber regelmäßig die Funktion qApp->processEvents() aufrufen, damit die Ereignisschleife abgearbeitet wird.

This post is licensed under CC BY 4.0 by the author.