Seite 1 von 1

andere Frage zu Threads, Signale und Slots

Verfasst: 24. September 2010 10:46
von rubikon
Moin.

Ich habe es dank der Hilfe in diesem tollen Forum geschafft über meine Anwendung über Sockets mit einer Hostanwendung zu verbinden und die dort gesendeten Daten richtig zu empfangen. Dafür nochmal vielen Dank an alle die mir geholfen haben.

Nun ist das 'Besondere' bei mir, dass das Verbinden in einem separaten Thread läuft:

Code: Alles auswählen

QSocketEngine::QSocketEngine(QObject *parent) :
    QThread(parent)
{
    ...
    ...
    // neuen Tcp-Socket erstellen
    m_pTcpSocket = new QTcpSocket();

    //--- Objekt in diesen Thread 'verschieben'
    m_pTcpSocket->moveToThread(this);

    connect(m_pTcpSocket,   SIGNAL(readyRead()),
            this,           SLOT(ReadFromSocket() ) );
    ...
    ...
}



void QSocketEngine::run()
{
    do
    {
        // Falls in Verwendung, Socket zurücksetzen
        m_pTcpSocket->abort();

        // Mit dem Server verbinden
        m_pTcpSocket->connectToHost("192.168.1.50", 4000);
    }
    while(!m_pTcpSocket->waitForConnected());

    exec();
}
Wenn ich das nun richtig verstanden habe, startet exec() ja die Event loop.

1. Heißt das, dass alle Events wie z.B SocketStateChanged() oder readyRead() im Kontext dieses Threads laufen?

Wenn ich nun in dem Slot ReadFromSocket() meine Daten empfangen habe, liegen diese in einem selbst definierten struct (welches noch ein union enthält) vor.

Diese Daten muss ich jetzt irgendwie in den Hauptthread bekommen wo diese die GUI verändern/aktualisieren soll.

2. Wie muss ich da vorgehen?
Wäre das die richtige Vorgehensweise: Eine Signal(zweiter Thread)/Solt(Haupthread mit GUI) einrichten. Dem Slot einen Zeiger der Daten mitgeben, mit dem er sich dann eine Kopie erstellt.
Würde mein zweiter Thread dann weiterlaufen, oder so lange blockiert sein, bis der Slot fertig ist?

Vielen Dank.

//Edit Listing ergänzt

Re: andere Frage zu Threads, Signale und Slots

Verfasst: 25. September 2010 14:02
von rubikon
rubikon hat geschrieben: Würde mein zweiter Thread dann weiterlaufen, oder so lange blockiert sein, bis der Slot fertig ist?
Soweit ich das jetzt rausgefunden habe, blockiert der zweite Thread, oder mach ich da was falsch?

Wird der Solt dann im Kontext des Hauptthreads ausgeführt, oder im Kontext des zweiten Threads?

Habe keine Möglichkeit gefunden das herauszubekommen. Wenn zweiteres der Fall ist, kann/darf ich dort ja nicht auf GUI Elemete zugreifen, oder?!?

Verfasst: 27. September 2010 08:08
von rubikon
*push*

Verfasst: 27. September 2010 11:59
von RHBaum
generell:
SIgnale und slots laufen in dem Thread, in dem das QObject erzeugt (Konstruktor durchlauf) wurde.

Dein erzeugter Thread macht also nix anders, als das connecten auszufuehren ^^ Die eigentliche Arbeit macht die die assynchronitaet des Threads (vom BS) und deine events liefert der Mainthread aus.

Generell:
Signale und Slots in verbindung mit nem QSocket machen eher sinn, wenn eigenes Multithreading vermeiden willst.
Nen Socket laeuft eh assynchron ....
willst du aber den Rest und das Handling der reinkommenden Daten komplett in nen eigenen thread haben, solltest du besser ohne die Events mehr mit schleifen arbeiten, und vor allem dein Socket im richtigen thread schon erzeugen ...

Tipp:
QSockets sind schoen, und einfach zu benutzen, aber gefaehrlich, weil man nicht mit der nase drauf gestossen wird, wie das zeugs in Wirklichkeit funktioniert.
Programmier mal lieber ein kleines Beispiel ohne QSockets, mit threads + raw sockets, da erkennst du das Wesen der Sockets viel besser.
Die Typische schleifenorientierte vorgehensweisse hat dann auch weiterhin Gueltigkeit unter Qt ...

Ciao ...

Verfasst: 27. September 2010 15:02
von rubikon
Blicke da immer noch nicht so wirklich durch... Wie sinnig das jetzige Konzept ist, möchte ich erstmal bei Seite lassen, weil ich das mit den Threads/Signals/Slots immer noch nicht verstanden habe...

Ich habe versucht mir mit der Windows API Funktion GetCurrentThreadId ausgeben zu lassen wann ich mich in welchem Thread (-kontext) befinde.

Das sieht in etwa so aus

Code: Alles auswählen

Client::Client(QWidget *parent) : QDialog(parent)
{
	qDebug() << "------------- MainThread Ctr. Beginn" <<  GetCurrentThreadId();

    ....
    ....
    //--- SocketEngine erstellen
    pSocketEngine = new QSocketEngine(this);

    pSocketEngine->start();

	qDebug() << "------------- MainThread Ctr. End" <<  GetCurrentThreadId();
}


void Client::ProcessData()
{
	qDebug() << "------------- MainThread ProcessData Beginn" <<  GetCurrentThreadId();
    Sleep(500);
	qDebug() << "------------- MainThread ProcessData End" <<  GetCurrentThreadId();
}

Code: Alles auswählen


#include "qSocketEngine.h"
#include "windows.h"

#include "client.h"

#define CONNECTDEUG

QSocketEngine::QSocketEngine(QObject *parent) :
    QThread(parent)
{
    qDebug() << "------------- SocketEngine ctr. Beginn" <<  GetCurrentThreadId();

    // neuen Tcp-Socket erstellen
    m_pTcpSocket = new QTcpSocket();

    //--- Objekt in diesen Thread 'verschieben'
    m_pTcpSocket->moveToThread(this);

    connect(this, SIGNAL(DataReady()),
            (Client *)parent, SLOT(ProcessData()) );

    connect(m_pTcpSocket,   SIGNAL(readyRead()),
            this,           SLOT(ReadFromSocket() ) );
    connect(m_pTcpSocket,   SIGNAL(error( QAbstractSocket::SocketError)),
            this,           SLOT(displayError(QAbstractSocket::SocketError) ), Qt::DirectConnection);
    connect(m_pTcpSocket,   SIGNAL(stateChanged(QAbstractSocket::SocketState)),
            this,           SLOT(SocketStateChanged(QAbstractSocket::SocketState)), Qt::DirectConnection);

    qDebug() << "------------- SocketEngine ctr. End" <<  GetCurrentThreadId();
}


void QSocketEngine::ReadFromSocket()
{
    qDebug() << "------------- SocketEngine ReadFromSocket Begin" <<  GetCurrentThreadId();
    while(m_pTcpSocket->bytesAvailable())
    {
        ....
        ....        
        emit(DataReady());
    }

    qDebug() << "------------- SocketEngine ReadFromSocket End" <<  GetCurrentThreadId();
}


void QSocketEngine::run()
{
    static int nConnect = 0;

    while(true)
    {
        do
        {
            qDebug() << "------------- SocketEngine run" <<  GetCurrentThreadId();
            nConnect++;
            blockSize = 0;

            // Falls in Verwendung, Socket zurücksetzen
            m_pTcpSocket->abort();

            // Mit dem Server verbinden
            m_pTcpSocket->connectToHost("192.168.1.50", 4000);

        }
        while(!m_pTcpSocket->waitForConnected());

        exec();

    }
}



void QSocketEngine::SocketStateChanged(QAbstractSocket::SocketState socketState)
{
    qDebug() << "------------- SocketEngine SocketStateChanged" <<  GetCurrentThreadId();

    if(socketState == QAbstractSocket::ClosingState)
    {
        exit(0);
    }

}
Nun habe ich folgende (hier ein wenig aufbereitete) Ausgabe gesehen:


//--- Ctr. Aufrufe...
------------- MainThread Ctr. Beginn 36044842
------------- SocketEngine ctr. Beginn 36044842
------------- SocketEngine ctr. End 36044842
------------- MainThread Ctr. End 36044842


//--- Pollen bis eine Verbindung zur Hostanwendung
//--- aufgebaut werden konnte
------------- SocketEngine run 75300898
------------- SocketEngine SocketStateChanged 75300898
------------- SocketEngine SocketStateChanged 75300898
------------- SocketEngine SocketStateChanged 75300898
------------- SocketEngine run 75300898
------------- SocketEngine SocketStateChanged 75300898
------------- SocketEngine SocketStateChanged 75300898
------------- SocketEngine SocketStateChanged 75300898
------------- SocketEngine run 75300898
------------- SocketEngine SocketStateChanged 75300898
------------- SocketEngine SocketStateChanged 75300898
------------- SocketEngine SocketStateChanged 75300898

//--- Daten empfangen
------------- SocketEngine ReadFromSocket Begin 36044842
------------- MainThread ProcessData Beginn 36044842
------------- MainThread ProcessData End 36044842
------------- SocketEngine ReadFromSocket End 36044842
Also hat doch der MainThread die ID 36044842 und der zweite Thread die ID 75300898, oder?

Wenn das so ist, bedeutet das, dass das Eventhandling im zweiten Thread stattfindet und das ReadFromSocket im MainThread?
Irgendwie blicke ich da nicht durch. :cry:

Ich hätte jetzt erwartet, das der ReadFromSocket Slot im Kontext des zweiten Threads ausgeführt wird.

Dort wird wieder ein Signal emitted.... und der Slot läuft dann im Hauptthread. Dem ist hier aber scheinbar nicht so.

Verfasst: 27. September 2010 16:21
von RHBaum
Es passiert folgendes:

Code: Alles auswählen

QSocketEngine::QSocketEngine(QObject *parent) :
    QThread(parent)
{
    qDebug() << "------------- SocketEngine ctr. Beginn" <<  GetCurrentThreadId();

    // neuen Tcp-Socket erstellen
    m_pTcpSocket = new QTcpSocket();

    //--- Objekt in diesen Thread 'verschieben'
    m_pTcpSocket->moveToThread(this);

    connect(this, SIGNAL(DataReady()),
            (Client *)parent, SLOT(ProcessData()) );

    connect(m_pTcpSocket,   SIGNAL(readyRead()),
            this,           SLOT(ReadFromSocket() ) );
    connect(m_pTcpSocket,   SIGNAL(error( QAbstractSocket::SocketError)),
            this,           SLOT(displayError(QAbstractSocket::SocketError) ), Qt::DirectConnection);
    connect(m_pTcpSocket,   SIGNAL(stateChanged(QAbstractSocket::SocketState)),
            this,           SLOT(SocketStateChanged(QAbstractSocket::SocketState)), Qt::DirectConnection);

    qDebug() << "------------- SocketEngine ctr. End" <<  GetCurrentThreadId();
} 
Du rufst den CTor auf. in diesem CTor erzeugst du das Socket Object (new) und verbindest es.
Der CTor laeuft im Mainthread. Das heisst der Erzeuger deiner Connections ist der Mainthread. Ergo alle indirekten Verbindungen (also die ueber threadgrenzen hinweg) werden auf den Mainthread gemappt.
Erwarten wuerd ich an der Stelle eher, das alles im Mainthread laeuft !
das m_pTcpSocket->moveToThread(this); aendert gar nix, weil Du bist doch im Mainthread (es sei denn du hasst noch irgendwo ne threadroutine, die QSocketEngine erzeugt, und damit den CTor von QSocketEngine ausführt. aber da hab ich nix gesehen)

Code: Alles auswählen

void QSocketEngine::run()
{
    static int nConnect = 0;

    while(true)
    {
        do
        {
            qDebug() << "------------- SocketEngine run" <<  GetCurrentThreadId();
            nConnect++;
            blockSize = 0;

            // Falls in Verwendung, Socket zurücksetzen
            m_pTcpSocket->abort();

            // Mit dem Server verbinden
            m_pTcpSocket->connectToHost("192.168.1.50", 4000);

        }
        while(!m_pTcpSocket->waitForConnected());

        exec();

    }
} 
So ok, hier bist nu in deinem erzeugten thread ....
du verbindest den Socket nur noch.

bleibt nur noch das "Phaenomen", warum dein SocketStateChanged im Neuen Thread laeuft:
Der connect und der constructor liefen ja im Mainthread.

Aber:
- m_pTcpSocket->connectToHost("192.168.1.50", 4000);
hier staeosst den connect an. Der wird sofort das statechanced werfen wollen.
DU uebergibst ihm ja sogar Direct als parameter, also macht er keinen Threadwechsel.
wuerde er indirekt connecten, wuerde das ding ueber die eventloop des mainthreads laufen.
Ergo -> direkte verbindung !
Funktioniert weil QT das erkennt, bzw alle infos schon zur verfuegung, wenn auf die eigene klasse connectest :-)

Verstanden ?

testweisse, versuch mal das m_pTcpSocket->moveToThread(this); in die run Methode vor den connect zu schieben ! Sollte eigentlich nen teilerfolg liefern.
Zum verstaendniss sicher ok ....

Wenn verstanden hasst was er tut, koennen wir drüber reden warum das weniger sinnvoll ist was du da tust, und wie man es besser macht.

Ciao ...

Verfasst: 28. September 2010 08:50
von rubikon
RHBaum hat geschrieben: Wenn verstanden hasst was er tut, koennen wir drüber reden warum das weniger sinnvoll ist was du da tust, und wie man es besser macht.
Sehr gerne, ist nur die Frage wie lang ich noch brauche um das hier zu verstehen ;-)

So langsam wird es mir klarer, aber noch nicht so ganz.

Ich habe mal versucht das m_pTcpSocket->moveToThread(this); ganz am Anfang der run Routine zu setzten.

Da bekomme ich aber Meldungen die gerade noch versuche zu verstehen:



//--- Ctr. Aufrufe...
------------- MainThread Ctr. Beginn 0x46b0066
------------- SocketEngine ctr. Beginn 0x46b0066
------------- SocketEngine ctr. End 0x46b0066
------------- MainThread Ctr. End 0x46b0066



//--- Pollen bis eine Verbindung zur Hostanwendung
//--- aufgebaut werden konnte
QObject::moveToThread: Current thread (0x126d60) is not the object's thread (0x122000).
Cannot move to target thread (0x126d60)

------------- SocketEngine run 0x5b0004a
------------- SocketEngine SocketStateChanged 0x5b0004a
------------- SocketEngine SocketStateChanged 0x5b0004a
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTcpSocket(0x127760), parent's thread is QThread(0x122000), current thread is QSocketEngine(0x126d60)
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTcpSocket(0x127760), parent's thread is QThread(0x122000), current thread is QSocketEngine(0x126d60)
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTcpSocket(0x127760), parent's thread is QThread(0x122000), current thread is QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged 0x5b0004a
------------- SocketEngine run 0x5b0004a
------------- SocketEngine SocketStateChanged 0x5b0004a
------------- SocketEngine SocketStateChanged 0x5b0004a
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTcpSocket(0x127760), parent's thread is QThread(0x122000), current thread is QSocketEngine(0x126d60)

Dann habe ich mir gedacht, ich erzeuge den TcpSocket auch in der run() routine und connecte ihn da auch.

Code: Alles auswählen

void QSocketEngine::run()
{
    static int nConnect = 0;

    // neuen Tcp-Socket erstellen
    m_pTcpSocket = new QTcpSocket();

    connect(m_pTcpSocket,   SIGNAL(readyRead()),
            this,           SLOT(ReadFromSocket() ) );
    connect(m_pTcpSocket,   SIGNAL(error( QAbstractSocket::SocketError)),
            this,           SLOT(displayError(QAbstractSocket::SocketError) ), Qt::DirectConnection);
    connect(m_pTcpSocket,   SIGNAL(stateChanged(QAbstractSocket::SocketState)),
            this,           SLOT(SocketStateChanged(QAbstractSocket::SocketState)), Qt::DirectConnection);

    //--- Objekt in diesen Thread 'verschieben'
   /* m_pTcpSocket->moveToThread(this); */ // scheint keinen Effekt zu haben

 while(true)
    {
        do
        {
            qDebug() << "------------- SocketEngine run" <<  GetCurrentThreadId();
            nConnect++;
            blockSize = 0;

            // Falls in Verwendung, Socket zurücksetzen
            m_pTcpSocket->abort();

            // Mit dem Server verbinden
            m_pTcpSocket->connectToHost("192.168.1.50", 4000);

        }
        while(!m_pTcpSocket->waitForConnected());

        exec();

    }
}
So wie ich das verstanden habe gehört das TcpSocket Objekt zu dem zweiten Thread und dementsprechend müsste der ReadFromSocket() Slot auch im Kontext des zweiten Threads ausgeführt werden.

Scheint aber nicht so zu sein:
//--- Ctr. Aufrufe...
------------- MainThread Ctr. Beginn 0x55c004a
------------- SocketEngine ctr. Beginn 0x55c004a
------------- SocketEngine ctr. End 0x55c004a
------------- MainThread Ctr. End 0x55c004a

//--- Pollen bis eine Verbindung zur Hostanwendung
//--- aufgebaut werden konnte
------------- SocketEngine run 0x4d6003a
------------- SocketEngine SocketStateChanged 0x4d6003a
------------- SocketEngine SocketStateChanged 0x4d6003a
------------- SocketEngine SocketStateChanged 0x4d6003a
------------- SocketEngine run 0x4d6003a
------------- SocketEngine SocketStateChanged 0x4d6003a
------------- SocketEngine SocketStateChanged 0x4d6003a
------------- SocketEngine SocketStateChanged 0x4d6003a
------------- SocketEngine run 0x4d6003a
------------- SocketEngine SocketStateChanged 0x4d6003a
------------- SocketEngine SocketStateChanged 0x4d6003a
------------- SocketEngine SocketStateChanged 0x4d6003a
------------- SocketEngine run 0x4d6003a
------------- SocketEngine SocketStateChanged 0x4d6003a
------------- SocketEngine SocketStateChanged 0x4d6003a
------------- SocketEngine SocketStateChanged 0x4d6003a

/--- Daten empfangen
------------- SocketEngine ReadFromSocket Begin 0x55c004a
------------- SocketEngine ReadFromSocket End 0x55c004a
------------- MainThread ProcessData Beginn 0x55c004a
------------- MainThread ProcessData End 0x55c004a
Was habe ich da falsch verstanden bzw. falsch gemacht?

Verfasst: 28. September 2010 11:30
von rubikon
Ich glaube ich habe es. Zumindest sieht die Ausgabe jetzt so aus wie ich sie mir vorstelle.

Zuerst mal habe ich bei der qDebug Ausgabe GetCurrentThreadId(); durch QObject::thread() ersetzt.

Nun sehe auch die Ids oder Handels oder was auch immer (sagen wir mal die 'Zahl'), welche mir schonmal als (Fehler-)Meldungen angezeigt wurden (z.B.QObject::moveToThread: Current thread (0x126d60) is not the object's thread (0x122000). )

Nun habe ich meinen Code wie folgt abgeändert

Code: Alles auswählen

Client::Client(QWidget *parent) : QDialog(parent)
{
    qDebug() << "------------- MainThread Ctr. Beginn" << QObject::thread();
	...
	...

    //--- SocketEngine (ohne Parent!!!) erstellen
    pSocketEngine = new QSocketEngine();

    connect(pSocketEngine, SIGNAL(DataReady()),
            this, SLOT(ProcessData()), Qt::QueuedConnection);

    pSocketEngine->start();

	qDebug() << "------------- MainThread Ctr. End" <<  QObject::thread() << endl;;
}



QSocketEngine::QSocketEngine(QObject *parent) :
    QThread(parent)
{
    qDebug() << "------------- SocketEngine ctr. Beginn" <<  QObject::thread();

    moveToThread(this);	

	// neuen Tcp-Socket erstellen
    m_pTcpSocket = new QTcpSocket();

    //--- Objekt in diesen Thread 'verschieben'
	m_pTcpSocket->moveToThread(this);

    connect(m_pTcpSocket,   SIGNAL(readyRead()),
            this,           SLOT(ReadFromSocket() ) );
    connect(m_pTcpSocket,   SIGNAL(error( QAbstractSocket::SocketError)),
            this,           SLOT(displayError(QAbstractSocket::SocketError) ), Qt::DirectConnection);
    connect(m_pTcpSocket,   SIGNAL(stateChanged(QAbstractSocket::SocketState)),
            this,           SLOT(SocketStateChanged(QAbstractSocket::SocketState)), Qt::DirectConnection);

	qDebug() << "------------- SocketEngine ctr. End" <<  QObject::thread() << endl;
}


void QSocketEngine::ReadFromSocket()
{
	qDebug() << "------------- SocketEngine ReadFromSocket Begin" <<  QThread::thread();
    while(m_pTcpSocket->bytesAvailable())
    {
		...
		...
        emit(DataReady());

        qDebug() << "bytesAvailable after read:" << m_pTcpSocket->bytesAvailable() << endl;
    }

    qDebug() << "------------- SocketEngine ReadFromSocket End" <<  QThread::thread();
}

Die Änderungen bestehen darin, das ich bei Erzeugen meines von QThread abgeleiteten Objects kein parent übergebe. So wie ich das verstanden habe darf man das nicht, da sonst alles im Mainthread laufen würde.

Dann stelle ich den Signal/Slot connect an dieser Stelle her.

Dann habe ich im Ctr meines von QThread abgeleiteten Objects moveToThread(this); aufgerufen. Ich habe zwar schonmal irgendwo gelesen, dass man das eigentlich nicht macht (obwohl anscheinend viele das machen), aber ohne funktioniert es nicht.

Das Erstellen des Sockets und das Verschieben in den zweiten Threads mache ich anschließend im Ctr.

Die Ausgabe sieht wie gewünscht aus:
//--- Ctr. Aufrufe...
------------- MainThread Ctr. Beginn QThread(0x122000)
------------- SocketEngine ctr. Beginn QThread(0x122000)
------------- SocketEngine ctr. End QSocketEngine(0x126d60)
------------- MainThread Ctr. End QThread(0x122000)

//--- Pollen bis eine Verbindung zur Hostanwendung
//--- aufgebaut werden konnte
------------- SocketEngine run QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine run QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine run QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine run QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine run QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine run QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)
------------- SocketEngine SocketStateChanged QSocketEngine(0x126d60)

/--- Daten empfangen
------------- SocketEngine ReadFromSocket Begin QSocketEngine(0x126d60)
------------- MainThread ProcessData Beginn QThread(0x122000)
------------- MainThread ProcessData End QThread(0x122000)
------------- SocketEngine ReadFromSocket End QSocketEngine(0x126d60)
-Main Ctr. startet und endet im Mainthread.

-Thread Ctr. startet im Mainthread, wird verschoben und endet im zweiten Thread.

- Event (welche durch das Pollen erzeugt werden), werden im zweiten Thread behandelt.

- Der ReadFromSocket() Slot wird im zweiten Thread ausgeführt. Dort wird wieder das DataReady() Singal emitted.

- Der empfangende Slot wird im Mainthread ausgeführt.


Damit ist zwar alles so wie ich mir das vorstelle. Aber wenn etwas so aussieht, muss das ja nicht heißen das es wirklich richtig implementiert ist. Deswegen meine Frage: Ist das so korrekt bzw. kann man das so machen (erstmal nur bezogen auf Thread/Signale/Slots)?

Verfasst: 28. September 2010 12:21
von RHBaum
Damit ist zwar alles so wie ich mir das vorstelle. Aber wenn etwas so aussieht, muss das ja nicht heißen das es wirklich richtig implementiert ist. Deswegen meine Frage: Ist das so korrekt bzw. kann man das so machen (erstmal nur bezogen auf Thread/Signale/Slots)?
Wenn es jetzt nur um Signale / Slots geht:
Sieht eigentlich schon gut aus. OK Signale und Slots selber sind auch gar ned so kompliziert. Es ist eher das zusammenspiel, was kompliziert ist.

generelles Q-Design:
Wie Du siehst, iss nachzuvollziehen, was in welchen Thread ausgefuehrt wird bei deinem minimalen Beispiel scho recht heftig :-)
Wenn ich sowas bauen muesst, wuerd ich es etwas übersichtlicher aufbauen.
movetothread meid ich immer. Movetothread hat aber eine daseinsberechtigung, IMHO aber eher im zusammenhang mit threadpools und vorher erzeugten objecten.
QSocketEngine iss IMHO bissi ein doofer Name.
Generell, da ich ned so genau weiss, was genau du machst, was genau das Ding am Ende koennen soll, isses schwer konkrete Tipps zu geben.

Generelles Socket design:
Eventloops erzeugen Overhead ! Du hasst eh immer eine in ner QApp. Ne zweite nur wenn sie Sinn macht ! Und Du brauchst sie ned wirklich !!!!
Nen Socket mit assynchronen Methoden arbeiten zu lassen in nem eigenen Thread macht weniger sinn. Das Ganze SIGNAL/SLOT Geroedel ist eigentlich vollkommen unnütz bei Dir :-)
Ergo. Sockets + Thread = schlechte vorrausetzung um Signale und Slots zu üben.

Ich weiss ned was genau du üben willst.
An deiner stelle würd ich andere Sachen probieren.
Signal / Slot iss nicht so komlex in der Anwendung, als das man dafuer allein so Zeit investieren muss.
Einzig etwas komlizierteres ist es im Zusammenhang mit Threads. welches Slot wird wo ausgeführt, und wie muss man die connecten.
Dazu iss dein Beispiel aber wirklich etwas ungeeignet.

Threads und eigene Eventloops ist aber schon wieder haerterer stoff. Viel eher wuerd ich mal anfangen, ein/mehrere threads ohne eventloop laufen zu lassen, und sowas selber von hand beenden zu lassen.
Threads an sich iss auch gar ned so komliziert, ne Kunst ist es nur die dinger kontrolliert runter fahren zu lassen. Was Dir aber mit der Eventloop wieder total abgeht.

Und noch nen Wort zum Schluss:
Viele Wege führen nach ROM. Aber nicht alle sind gleich.
Die Wege, die die meisten verwenden sind am besten ausgebaut, und man kann anhand diesen besser erklären, man findet mehr kartenmaterial dazu im Netz usw.
Was ich damit sagen will:
Dinge programmieren und zum laufen zu bringen kann jeder über kurz oder lang. Die hohe Schule bei der Programmierung ist, Code zu schreiben, den man selbst nach 10 Jahren noch versteht und den auch andere einfach verstehen zu können.
Dabei hilft es oft ungemein, Technicken zu verwenden, die andere zuvor schon etabliert haben. In deinem Falle:
Sockets so benutzen, wie nen C/C++ Programmierer Sockets intuitiv benutzen würd. ^^

Ciao ...

Verfasst: 30. September 2010 11:50
von rubikon
Erstmal vielen Dank für Deine ausführlichen Antworten.

Meine Situation ist Folgende: Ich habe eine Anwendung welche sich über Sockets mit einer Hostanwendung verbinden soll. Da die Hostanwendung später gestartet werden kann bzw. zwischen durch beendet und wieder gestartet werden kann, gabe ich diesen Pollingmechanismus gemacht.

Dies wollte ich in einem separaten Thread haben. Auch das Empfangen (und später auch Senden) der Daten über Sockets wollte ich in einem separaten Thread haben, damit die Anwendung noch bedienbar ist. Deswegen dachte ich mir, das es am übersichtlichsten ist, das ich die ganze Socketfunktionalität in einen eigen Klasse packe. Ob der Name nun gut gewählt ist oder nicht, sei mal dahingestellt, aber mir ist kein besserer Name eingefallen.

Damit der Kram in einem eigenen Thread läuft habe ich meine neue Klasse von QThread abgeleitet.

So, nun verändern die empfangen Daten die GUI meiner Anwendung z.b. werden Beschriftungen und Farben von Labels geändert, oder Dialoge (modal) eingeblendet bzw. wieder ausgeblendet.

Da man dies ja nur im Hauptthread machen darf, dachte ich das ich nun mit Signals und Slots arbeiten sollte/müsste.

Es funktioniert jetzt zwar, aber wenn Du mir sagen würdest wie ich das ganze besser designen sollte, würde ich mich freuen :-)

Verfasst: 4. Oktober 2010 09:01
von RHBaum
Prinzipiell, fuer ressourcen assynchroner Natur gibt es, je nach BS, meist 2 varianten, wie man die nutzen kann, synchron und assynchron :-)
Bei Sockets ist es per definition so !

Assynchron: steuert deine aktionen an, und wenn irgendwelche ereignisse auftreten, werden die in deinen Thread gemappt.

Synchron: das betriebssystem steuert deine Akrion an, wartet aber auf ergebnisse fuer dich. Die Dinger sehen dann aus wie synchrone funktionen.
Und wenn ein BS was ordentlich kann, dann ist es warten :-) Besser als man es selber implementieren kann :-)

assynchron verwendet man tendentiell dann, wenn man sich nen eigenen thread sparen will. Alle ereignisse werden dann im mainthread abgehandelt. Wenn die verarbeitung der daten ned so lang dauert, bzw oft Luft ist zwischen ende verarbeitung und neuen empfang, langt das oft fuer ein nichtblocken der anwendung.
Erzeugt man aber mal Last, zeiht es die Oberflaeche mit runter, ergo die Anwendung wird zaeh !

Synchron verwendet man eher in einfachen abfragen, oder wenn man soweiso eigene Threads macht ....
gabe ich diesen Pollingmechanismus gemacht.
Pollen hat zwar nen schlechten ruf, ist aber oft unumgaenglich :-) viele Dinge realisiert das BS selber durch pollen.
Die grosse kunst beim programmieren ist das zu umgehen, und das das BS machen zu lassen, weil das kann es besser :-)
Dies wollte ich in einem separaten Thread haben.
Durchaus verstaendlich und kein Problem :-)
Deswegen dachte ich mir, das es am übersichtlichsten ist, das ich die ganze Socketfunktionalität in einen eigen Klasse packe.
Auch ok ....
Da man dies ja nur im Hauptthread machen darf, dachte ich das ich nun mit Signals und Slots arbeiten sollte/müsste.
Richtig, und ja Signale/Slots sind nen guter weg dazu ...

Wenn du snychron arbeitest, kannst du dir die eigene eventloop sparen.

Du leitest weiterhin QSocketEngine von QThread ab.
Erzeugst deinen Socket aber nu da wo ihn brauchst .... im richtigen Thread, ergo in der Run methode ....

Code: Alles auswählen

void QSocketEngine::run()
{
    QTCPSocket mySock;
 
    /// endlosSchleife für connectionversuche 
    while(!checkAbort())  /// checkAbort ist eine Funktion zum testen einer Abbruchbedingung von aussen
    {
         /// Versuchen zu verbinden 
         mySock.connect("192.168.1.50", 4000);
         /// warten ob connected ! mit einstellbaren intervall
         /// sehr ressourcenschonend, wenn hohe werte gewaehlt ... 
         if(mySock.waitForConnected(1000))  /// mal jede sekunde versuchen
         {
               /// wir wurden verbunden .... 
               /// nun einfach die daten auslesen 
               const char BufferSize = 512; /// Mal 512 byte als buffer nehmen
               QByteArray buffer(512,0);
               /// und Blockierend lesend auf den Socket Schicken 
               /// in ner Schleife 
               do
               {
                    qint64 bytesRead = mySock.read(buffer.data(),buffer.size());
                    if(bytesRead > 0) 
                    {
                        /// wir haben irgendwas gelesen, dass gleich an die Verarbeitungsfunktion weitergen 
                        /// im gleichen thread wenns geht ... 
                        /// performData(buffer.data(),bytesRead );
                    }
               } while (!checkAbort() && /// hier eigene Abbruchbedingungen checken etc. und vielleicht auch den SocketStatus)
              /// wir waren Connected und disconecten und nun, weil wir normal(oder unnormal) rausgeflogen sind ... 
             mySock.disconnectFromHost();
             /// Gnadenfrist geben 
             waitForDisconnected (30000);
         }
 
    }
 
} 
Prinzip verstanden ?

wie gesagt, die grosse kunst iss das Ding "sauber" auslaufen zu lassen.
Vorbereitet haben wir ja das ueber die checkAbort methode.
Die sollte threadsicher auf ne Variable zugreifen ...

wir definieren an der QSocketEngine eine member vom Typ bool mbAbort, die anzeigt das man abbrechen soll, und nen Mutex Um die zu schuetzen QMutex mcsAbort;

dann wuerde man sowas machen:

Code: Alles auswählen

bool QSocketEngine::checkAbort()
{
    QMutexLocker(&mcsAbort); 
    return mbAbort;
}

void QSocketEngine::setAbort(bool bAbort = true)
{
    QMutexLocker(&mcsAbort); 
    mbAbort = bAbort;
}
setAbort und checkAbort koennen nun von unterschiedlichen threads verwendet werden, ohne das es threadbedingt zu unsauberen verhalten kommen sollte.
Einige wuerden anmerken das der mutex überfluessig ist ... weil ne zuweissung auf nen 32bit wert zu hoher wahrscheinlichkeit ne atomare operation ist ...
Es ist aber nicht garantiert, ich bin fuer die saubere version ...

Um nun von aussen deinen Thread runterzufahren, koenntest du sowas machen ....

Code: Alles auswählen

void QSocketEngine::ShutdownConnectionThread
{
     setAbort(true); 
     /// nun sollte parallel der Thread sich runnerfahren ... 
     /// ihm die Gnadenfrist geben und warten 
     if(!wait(10000)) /// geben wir ihm mal 10 sec 
     {
          /// in wenn er es in der zeit nicht geschafft hat ...
          /// den thread ganz boese beenden ! 
          terminate ();
          /// wir hatten ja keine andere Wahl. 
          /// am besten noch die Appp benachrichtigen, dass der thread ned sauber beendete und paar dinge im undefinierten zustand sind ... 
          /// am besten ne Exception werfen oder so ! 
          throw(EThreadTerminated() )
     }
}
Ciao ...