SQL-Datenbank und Threads

Alles rund um die Programmierung mit Qt
C167
Beiträge: 105
Registriert: 9. Februar 2008 20:30

SQL-Datenbank und Threads

Beitrag von C167 »

Hi,
ich hab eine SQLite-Datenbank, auf die sowohl aus dem GUI-Thread als auch aus einem Worker-Thread zugegriffen wird (werden soll). Der GUI-Thread liest nur reine Informationen aus, der Worker-Thread allerdings Binaerdaten, was laenger dauert und die GUI blockieren wuerde. Nun habe ich da einige Probleme, v.a. weil ich neu auf dem Gebiet des Multithreading bin. Also, erstmal der Thread:
header:

Code: Alles auswählen

#ifndef BINARYLOADER_H
#define BINARYLOADER_H

#include <QImage>
#include <QObject>
#include <QSize>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>

const int TYPE_BINARY = 0;
const int TYPE_IMAGE  = 1;

class BinaryLoader : public QThread
{
		Q_OBJECT
	public:
		BinaryLoader (  );
		~BinaryLoader ( );
	public slots:
		void getData ( QString, int, const int type = TYPE_BINARY );
	signals:
		void binaryLoaded ( int handle, QByteArray );
		void imageLoaded ( int handle, QImage );
	protected:
		void run();
	private:
		QMutex mutex;
		QWaitCondition condition;

		bool restart;
		bool working;
		int idx, type, handle;
		QString tableName;
		QSize size;
};
#endif // BINARYLOADER_H
cpp:

Code: Alles auswählen

#include <QtSql>
#include "BinaryLoader.h"

BinaryLoader::BinaryLoader ( ) : QThread ( ),
		restart ( false ), working ( false ), idx ( -1 )
{
}

BinaryLoader::~BinaryLoader()
{
	mutex.lock();
	condition.wakeOne();
	mutex.unlock();
	wait();
}

void BinaryLoader::getData ( QString tableName, int id, const int type )
{
	mutex.lock();
	this->idx = id;
	this->tableName = tableName;
	this->type = type;
	mutex.unlock();

	qDebug() << tableName << "\t" << id;
	if ( !isRunning() )
		start();
	else
	{
		// already running, wake up whenever ready
		restart = true;
		condition.wakeOne();
	}
}

void BinaryLoader::run()
{
	qDebug() << "run()";
	// copy necessary data
	mutex.lock();
	this->working = true;
	QString tableName = this->tableName;
	int index = this->idx;
	int t = this->type;
	mutex.unlock();

	bool good = true;
	QString connectionName = QString ( "threadDB_%1_%2" ).arg ( tableName ).arg ( index );
	QSqlDatabase db = QSqlDatabase::addDatabase ( "QSQLITE", connectionName );
	db.setDatabaseName ( QSqlDatabase::database().databaseName () );
	db.open();
	QSqlQuery query ( db );
	query.prepare ( QString ( "SELECT data FROM %1 WHERE id = :id" ).arg ( tableName ) );
	query.bindValue ( ":id", index+1 );
	if ( !query.exec() ) good = false;
	if ( !query.next() ) good = false;

	if ( good )
	{
		switch ( t )
		{
			case TYPE_BINARY:
				emit binaryLoaded ( handle, query.value ( 0 ).toByteArray ( ) );
				break;
			case TYPE_IMAGE:
				emit imageLoaded ( handle, QImage::fromData ( query.value ( 0 ).toByteArray ( ) ) );
				break;
			default:
				break;
		}
	}

	db.close();
	db = QSqlDatabase();

	// we are ready
	mutex.lock();
	this->working = false;
	mutex.unlock();

	// put to sleep
	mutex.lock();
	if ( !this->restart )
		condition.wait ( &mutex );
	restart = false;
	mutex.unlock();
}
Ich rude den Thread nun mehrfach mit folgendem Aufruf auf:

Code: Alles auswählen

loader->getData ( QString ( "splashes" ), 0, TYPE_IMAGE );
loader->getData ( QString ( "splashes" ), 1, TYPE_IMAGE );
loader->getData ( QString ( "splashes" ), 2, TYPE_IMAGE );
loader->getData ( QString ( "splashes" ), 3, TYPE_IMAGE );
und habe ihn per

Code: Alles auswählen

connect ( loader, SIGNAL ( imageLoaded ( int, QImage ) ), this, SLOT ( display ( int, QImage ) ) );
angeschlossen. So, nun wird das erste Bild geladen, dann keines mehr. Ich hab herausgefunden (qDebug) dass die run-methode nur beim ersten mal aufgerufen wird. Die Debugging-Ausgabe:
qDebug() hat geschrieben:"splashes" 0
run()
"splashes" 1
"splashes" 2
"splashes" 3
Ich vermute dass ich mich entweder mit dem Mutex vertan habe oder dass die restart-Funktionen nicht funktionieren.
patrik08
Beiträge: 746
Registriert: 27. Februar 2006 10:48
Wohnort: DE Freiburg

Beitrag von patrik08 »

Im nicht! GUI-Thread kann man keine bilder erstellen ... nur die daten auslesen die dann im GUI-Thread mit qpixmap loadfromdata stream laden..

darum habe ich extra eine bild klasse erstellt um bilder zu serialisieren um jederzeit auf QByteArray data; zuzugreifen koennen...
da eben in einem nicht GUI-Thread mit datastream qpixmap nichts passiert.

http://www.qtforum.de/forum/viewtopic.php?t=6600
.........................
speack português italiano deutsch english castellà qt
C167
Beiträge: 105
Registriert: 9. Februar 2008 20:30

Beitrag von C167 »

patrik08 hat geschrieben:Im nicht! GUI-Thread kann man keine bilder erstellen ... nur die daten auslesen die dann im GUI-Thread mit qpixmap loadfromdata stream laden..
darum habe ich extra eine bild klasse erstellt um bilder zu serialisieren um jederzeit auf QByteArray data; zuzugreifen koennen...
da eben in einem nicht GUI-Thread mit datastream qpixmap nichts passiert.

http://www.qtforum.de/forum/viewtopic.php?t=6600
aehm... interessant, denn das erste Bild wird absolut Fehlerfrei geladen, und weitere wuerden das auch, wenn die run-Methode aufgerufen werden wuerde (bin mir da ziemlich sicher). Auch hab ich da nichts mit Pixmaps zu tun, die werden erst spaeter im aufgerufenen Slot erstellt, der ausserhalb des Threads liegt. Nun bin ich noch etwas ratloser ;)
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Ich finde die Logik nicht so gut.
Thread anstarten, per exec() in die EventLoop -> Signal/Slot Verbindung von Gui-Thread nach Thread machen. Damit dann Daten anfordern. Dieses ganze Mutex-Zeugs entfällt so mehr oder weniger.
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
C167
Beiträge: 105
Registriert: 9. Februar 2008 20:30

Beitrag von C167 »

Christian81 hat geschrieben:Ich finde die Logik nicht so gut.
Ich lerne es ja gerade ;)
Christian81 hat geschrieben:Thread anstarten, per exec() in die EventLoop -> Signal/Slot Verbindung von Gui-Thread nach Thread machen. Damit dann Daten anfordern. Dieses ganze Mutex-Zeugs entfällt so mehr oder weniger.
Hm... laeuft dann der Thread permanent mit? Das wuerde bedeuten, dass er Thread auf Daten aus den Member-Variablen wartet, und wenn neue da sind (bool-Paramenter beim Daten liefern setzen und der Thread wartet auf ein true?) die abarbeiten?
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Nix bool und warten - Einfach mit exec() in die Eventloop rein. Dann kommt ein Signal vom Main-thread und der Slot im Thread wird ausgeführt, fertig. Schau Dir bitte mal die Beispiele an.
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
C167
Beiträge: 105
Registriert: 9. Februar 2008 20:30

Beitrag von C167 »

Ich hab bisher nur das gefunden:
Qt Assistant hat geschrieben:An event loop in a thread makes it possible for the thread to use certain non-GUI Qt classes that require the presence of an event loop (such as QTimer, QTcpSocket, and QProcess). It also makes it possible to connect signals from any threads to slots of a specific thread. When a signal is emitted, the slot isn't called immediately; instead, it is invoked when control returns to the event loop of the thread to which the object belongs. The slot is executed in the thread where the receiver object lives. See QObject::connect() for details.
Demnach koennte ich beim Aufruf des Konstruktors den Thread per start() starten, in der Run-Methode die Verbindung zur DB herstellen und QThread::exec() aufrufen. Ein Slot wird dann von einem Signal aus dem Main-Thread aufgerufen und beginnt seine Arbeit im Kontext des Threads mit dessen Datenbankverbindung? Hm... muss dazu der Slot per Signal aufgerufen werden oder ginge das auch wenn ich ihn wie jede andere Methode aufrufe, das wuerde mit mehrere Signale einsparen?
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Es geht nur per Signal-Slot - ein direkter Aufruf ändert ja nicht den Thread-Kontext.
Die Verbindung muss außerdem queued sein.
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
C167
Beiträge: 105
Registriert: 9. Februar 2008 20:30

Beitrag von C167 »

Okay, ich rufe den Thread wie folgt auf:

Code: Alles auswählen

connect ( this,   SIGNAL ( loadImage ( QString, int, int ) ), loader, SLOT ( getData ( QString, int, int ) ), Qt::QueuedConnection );
connect ( loader, SIGNAL ( imageLoaded ( int, QImage ) ), this, SLOT ( display ( int, QImage ) ) );

emit loadImage ( QString ( "splashes" ), 0, TYPE_IMAGE );
emit loadImage ( QString ( "splashes" ), 1, TYPE_IMAGE );
emit loadImage ( QString ( "splashes" ), 2, TYPE_IMAGE );
emit loadImage ( QString ( "splashes" ), 3, TYPE_IMAGE );
wobei die der aufgerufene Slot display so aussieht:

Code: Alles auswählen

void MainPage::display ( int handle, QImage image )
{
	switch ( handle )
	{
		case 0:
			p0 = new QPixmap ( QPixmap::fromImage ( image ) );
			splash0->setPixmap ( ( *p0 ).scaled ( QSize ( 930, 762 ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) );
			break;
		case 1:
			p1 = new QPixmap ( QPixmap::fromImage ( image ) );
			splash1->setPixmap ( ( *p1 ).scaled ( QSize ( 930, 762 ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) );
			break;
		case 2:
			p2 = new QPixmap ( QPixmap::fromImage ( image ) );
			splash2->setPixmap ( ( *p2 ).scaled ( QSize ( 930, 762 ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) );
			break;
		case 3:
			p3 = new QPixmap ( QPixmap::fromImage ( image ) );
			splash3->setPixmap ( ( *p3 ).scaled ( QSize ( 930, 762 ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) );
			break;
	}
}
So, der Thread, Header:

Code: Alles auswählen

class BinaryLoader : public QThread
{
		Q_OBJECT
	public:
		BinaryLoader (  );
		~BinaryLoader ( );

	public slots:
		void getData ( QString, int, const int type = TYPE_BINARY );

	signals:
		void binaryLoaded ( int handle, QByteArray );
		void imageLoaded ( int handle, QImage );

	protected:
		void run();

	private:
		QString connectionName;
};
#endif // BINARYLOADER_H
und cpp:

Code: Alles auswählen

#include <QtSql>
#include <QTime>
#include "BinaryLoader.h"

BinaryLoader::BinaryLoader ( ) : QThread ( )
{
	start();
}

BinaryLoader::~BinaryLoader()
{
	QSqlDatabase db = QSqlDatabase::database ( connectionName );
	db = QSqlDatabase();
}

void BinaryLoader::getData ( QString tableName, int id, const int type )
{
	bool good = true;
	QSqlQuery query ( QSqlDatabase::database ( connectionName ) );
	query.prepare ( QString ( "SELECT data FROM %1 WHERE id = :id" ).arg ( tableName ) );
	query.bindValue ( ":id", id+1 );
	if ( !query.exec() ) good = false;
	if ( !query.next() ) good = false;

	if ( good )
	{
		switch ( type )
		{
			case TYPE_BINARY:
				emit binaryLoaded ( id, query.value ( 0 ).toByteArray ( ) );
				break;
			case TYPE_IMAGE:
				emit imageLoaded ( id, QImage::fromData ( query.value ( 0 ).toByteArray ( ) ) );
				break;
			default:
				break;
		}
	}
}

void BinaryLoader::run()
{
	qsrand ( QTime::currentTime().msec() );
	connectionName = QString ( "thread_%1" ).arg ( qrand() );
	QSqlDatabase db = QSqlDatabase::addDatabase ( "QSQLITE", connectionName );
	db.setDatabaseName ( QSqlDatabase::database().databaseName () );
	db.open();
	exec();
}
Momentan wird das Fenster geoeffnet, aber es erscheint nichts. Erst nach einer Zeit erscheint alles auf einmal, inklusive der geladenen Bilder. So gesehen funktioniert das, nur das Blockieren mit grauem Hintergrund ist nicht so schoen. Wobei, nach einiger Zeit ist, gut, es dauert ca. 3 Sekunden, vorher erschien das Fenster erst nach 10 Sekunden.
Ausserdem bekomm ich nun 3 Warnungen
QPixmap::scaled: Pixmap is a null pixmap
QPixmap::scaled: Pixmap is a null pixmap
QSqlDatabasePrivate::removeDatabase: connection 'thread_1471841391' is still in use, all queries will cease to work.
Wobei die letzte erst beim Beenden auftaucht... Auch wenn ich das

Code: Alles auswählen

db = QSqlDatabase();
direkt nach dem exec()-Aufruf mache...
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Ich glaube da helfen nur ein paar qDebug() - Ausgaben bzw. debuggen - Auf den ersten Blick sieht es soweit ok aus. Die zweite Connection sollte sicherheitshalber auch queded sein (eigentlich macht Qt das selbst, aber manchmal, wenn z.B. der Thread noch nicht läuft oder so, wird es nicht erkannt). Man kann z.B. auch die Bilder einfach mal speichern um zu schauen was da drin ist.
Außerdem sollten die Übergabeparameter const Referenzen sein damit unnötige Kopien vermieden werden. Die connect() sind davon unberührt - dort muss weiterhin QString stehen anstatt const QString&.
Warum merkst Du Dir p0 bis p3 bzw. warum sind das Pointer? Die Konvertierung von QImage nach QPixmap kostet auch Zeit - also auch im Thread machen.
Was in ~BinaryLoader() gemacht wird verstehe ich nicht - sinnlos.
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
C167
Beiträge: 105
Registriert: 9. Februar 2008 20:30

Beitrag von C167 »

Christian81 hat geschrieben:Ich glaube da helfen nur ein paar qDebug() - Ausgaben bzw. debuggen - Auf den ersten Blick sieht es soweit ok aus.
Okay
Christian81 hat geschrieben:Die zweite Connection sollte sicherheitshalber auch queded sein (eigentlich macht Qt das selbst, aber manchmal, wenn z.B. der Thread noch nicht läuft oder so, wird es nicht erkannt). Man kann z.B. auch die Bilder einfach mal speichern um zu schauen was da drin ist.
Mhm, ich muss eh ein paar Bilder wiederherstellen, da kann ich das gleich machen
Christian81 hat geschrieben: Außerdem sollten die Übergabeparameter const Referenzen sein damit unnötige Kopien vermieden werden. Die connect() sind davon unberührt - dort muss weiterhin QString stehen anstatt const QString&.
Also die Methode getData umbauen in

Code: Alles auswählen

void getData ( const QString, const int, const int type = TYPE_BINARY )
??
Christian81 hat geschrieben:Warum merkst Du Dir p0 bis p3 bzw. warum sind das Pointer?
Och, naja, ich brauche die dinger, die enthalten die eientlichen Bilder. Da die beim aufziehen des Fensters mitvergroessert werden sollen, gehe ich da immer von den ursprungsbildern aus. Und Pointer: naja, warum denn nicht?
Christian81 hat geschrieben:Die Konvertierung von QImage nach QPixmap kostet auch Zeit - also auch im Thread machen.
Okay, da haette ich eigentlich selber draufkommen koennen ;) thx.
Hm.. kleine Zwischenfrage: Was dauert laenger: Resize (wie in der Funktion oben) oder ein Bild in ein QLabel setzen? Ich trage mich gerade mit dem Gedanken, das Resizen der Bilder in einen Thread auszulagern und per ResizeEvent anstossen zu lassen...
Christian81 hat geschrieben:Was in ~BinaryLoader() gemacht wird verstehe ich nicht - sinnlos.
Hm... ich versuch da die Verbindung beim Aufloessen zu beenden, aber das funktioniert da nicht. Eigentlich muesste der Destruktor aufgerufen werden, wenn das Programm beendet wird. Ich hab auch versucht, die DB direkt nach dem Aufruf von exec() also wenn der loop verlassen wird zuzumachen... geht nicht
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Ich meinte const References, nicht const Objekte -> const QString& (c++ basics)

Wie lange resize() dauert kommt drauf an. Auch wie oft das passiert etc. Ich würde es erstmal nicht auslagern.

Wegen dem dtor - du machst dort nichts als db einen Wert zuzuweisen und dann einen anderen. Du suchst ggf. QSqlDatabase::removeDatabase() oderQSqlDatabase::close()
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
C167
Beiträge: 105
Registriert: 9. Februar 2008 20:30

Beitrag von C167 »

Christian81 hat geschrieben:Ich meinte const References, nicht const Objekte -> const QString& (c++ basics)
Ich hab mit den Begriffen noch so meine Probleme... :oops: also so?

Code: Alles auswählen

void getData ( const QString&, const int&, const int type = TYPE_BINARY );
Christian81 hat geschrieben:Wie lange resize() dauert kommt drauf an. Auch wie oft das passiert etc. Ich würde es erstmal nicht auslagern.
Da es ueber das ResizeEvent getriggert wird, relativ oft wenn man das Fenster resized
Christian81 hat geschrieben:Wegen dem dtor - du machst dort nichts als db einen Wert zuzuweisen und dann einen anderen. Du suchst ggf. QSqlDatabase::removeDatabase() oder QSqlDatabase::close()
Hm... ich hab sowohl close als auch QSqlDatabase::removeDatabase( connectionName ) eingefuegt, und zwar direkt nach dem exec(); aber es aendert nichts daran
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Const references haben nur bei non-POD Types Sinn - lies Dich da bitte mal etwas ein :)
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
C167
Beiträge: 105
Registriert: 9. Februar 2008 20:30

Beitrag von C167 »

POD=Plain Old Data?
dtor, auf ctor bin ich auch gestossen... das kann ja heiter werden
¤dit: aha, ConstrucTOR und DestrucTOR
Antworten