QThread richtig einsetzen



  • Hi,

    ich bin deine Vorschlag umgesezt in dem ich von CAgilentLan einen membervariable erstellt habe:

    CAgilentLan* m_workerObject;
    

    dann habe ich der Konstruktor der Klasse, wo der Object "CAgilentLan" gebraucht folgende geschrieben:

    QThread * workerThread = new QThread();
    	workerObject = new CAgilentLan(settings_l.strfuGetIP_DSO(), 500, settings_l.bofuGetSimulate());
    	workerObject->moveToThread(workerThread);
    
    	connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
    	connect(workerThread, &QThread::finished, workerObject, &CAgilentLan::deleteLater)
    

    bei der destruktor der Class habe ich folgende:

    if(m_workerObject != NULL)
    	{
    		delete m_workerObject;// hier knallt
    	}
    

    ich bekomme folgende Fehlermeldung:

    ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread 9c03e8. Receiver '' (of type 'QNativeSocketEngine') was created in thread 4c3a838", file kernel\qcoreapplication.cpp, line 541
    


  • Diese Error tritt sobald die Application beendet wird.



  • Hi,

    wenn du den Thread auch im Konstruktor erstellst, würde ich daraus auch eine Member Variable machen. Du möchtest den ja hinterher mit

    workerThread -> start();
    

    aufrufen. Und das geht nur, wenn der Thread an der Stelle des aufrufes bekannt ist.

    Der Grund für die Assertion ist das du delete aufrufst, das Objekt aber inzwischen im Thread Context lebt. Außerdem hast du mit deleteLater das Objekt schon zum löschen freigegeben, nach dem finished aufgerufen wird (also the Thread beendet wurde).

    Da ich hier grade keine QT Installation zum ausprobieren habe, gibt mir Google für das Connect aber folgende Syntax:

    QThread * workerThread = new QThread();
    workerObject = new CAgilentLan(settings_l.strfuGetIP_DSO(), 500, settings_l.bofuGetSimulate());
    workerObject->moveToThread(workerThread);
    
    connect(workerObject , SIGNAL(finished()), workerObject , SLOT(deleteLater()));
    connect(workerThread , SIGNAL(finished()), workerThread , SLOT(deleteLater()));
    

    Das heißt, wenn der Thread das SIGNAL finished() schickt, werden die Objekte zum löschen freigegeben. Das übernimmt dann QT und muss nicht nochmal von Hand im Destruktor gemacht werden.
    Wenn du den Thread und den CAgilentLan in der Klasse häufiger verwenden willst, darfst du sie nicht mit dem ersten "finished" Signal löschen.



  • Schlangenmensch schrieb:

    connect(workerObject , SIGNAL(finished()), workerObject , SLOT(deleteLater()));
    connect(workerThread , SIGNAL(finished()), workerThread , SLOT(deleteLater()));
    

    Das ist noch die "alte" syntax. Mit Qt5 wurde die syntax erweitert damit man auch signale z.b. mit einem lambda verknüpfen kann:
    https://woboq.com/blog/new-signals-slots-syntax-in-qt5.html



  • Argh, my fault... das kommt davon wenn man nicht aufpasst und Google noch schnell QT 4.x Dokus raus haut.



  • Hi,

    ich muss noch mal fragen "Sorry"
    So sieht es aus die Connect Funktionen in der Konstruktor:

    connect(m_pvWorkerThread, &QThread::finished, m_pvWorkerThread, &QThread::deleteLater);
    	connect(m_pvAgilentWorker, &CAgilentLan::finished, m_pvAgilentWorker, &CAgilentLan::deleteLater);
    

    die

    m_pvWorkerThread->start();
    

    Wo soll ich die bitte aufrufen?
    Wenn ich die in der Konstruktor aufrufe dann knallt. Wenn ich die auch gar nicht aufrufe dann knallt auch aber in eine andere Stelle "Wo das Programm beendet wird".



  • Du musst das an der Stelle aufrufen, an der du möchtest, dass der Thread anfängt seine Aufgaben abzuarbeiten.

    Was für einen Fehler bekommst du denn, wenn du das im Konstruktor aufrufst? Eigentlich sollte das egal sein.
    Und wo stürzt er dann genau ab? Sicher nicht bei

    m_pvWorkerThread->start();
    

    , oder?



  • Schlangenmensch schrieb:

    Du musst das an der Stelle aufrufen, an der du möchtest, dass der Thread anfängt seine Aufgaben abzuarbeiten.

    Was für einen Fehler bekommst du denn, wenn du das im Konstruktor aufrufst? Eigentlich sollte das egal sein.
    Und wo stürzt er dann genau ab? Sicher nicht bei

    m_pvWorkerThread->start();
    

    , oder?

    An diese Stelle nicht.

    m_pvWorkerThread->start()
    

    aber bei Erzeugung das Object wo die CAgilent aufgerufen wird.
    Fehlermeldung:

    ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread 4c19f68. Receiver '' (of type 'QNativeSocketEngine') was created in thread 4c582e0", file kernel\qcoreapplication.cpp, line 541
    


  • Das scheint wieder am löschen des Objektes zu liegen.

    Zum einen würde ich dir empfehlen en QThread nicht mit "new" zu erstellen, sondern einfach auf dem Stack.

    Zum anderen, schau dir die Zeile nochmal an:

    connect(m_pvAgilentWorker, &CAgilentLan::finished, m_pvAgilentWorker, &CAgilentLan::deleteLater)
    

    Du willst das Objekt löschen, wenn der Thread fertig ist. Also muss welches Objekt der Sender sein?

    Außerdem, wenn du das häufiger verwenden möchtest (was ja der Ausgangspunkt war), willst du das nicht nach dem finished Signal des Threads löschen, sondern ja eventuell nochmal verwenden.



  • Hallo,
    Ich habe das Connect so angepasst:

    connect(m_pvWorkerThread, &QThread::finished, m_pvWorkerThread, &CAgilentLan::deleteLater);
    

    Beim Application Starten bekomme ich eine Fehlermeldung "MyApllication.exe has triggered a breakpoint"in folgende Codeabschnitt:

    static void deallocate(QArrayData *data)
        {
            Q_STATIC_ASSERT(sizeof(QTypedArrayData) == sizeof(QArrayData));
            QArrayData::deallocate(data, sizeof(T), Q_ALIGNOF(AlignmentDummy));
        }
    

    Egal wie ich das umdrehe habe ich stets die gleiche Fehlermeldung.



  • Was sind denn die letzten Zeilen Code die aufgerufen werden und die du geschrieben hast? Sollte sich aus dem Callstack ablesen lassen.



  • Hi,

    Schlangenmensch schrieb:

    Was sind denn die letzten Zeilen Code die aufgerufen werden und die du geschrieben hast? Sollte sich aus dem Callstack ablesen lassen.

    In der Konstruktur wo das Object

    CAgilentLan
    

    aufgerufen wird folgende Code geschrieben:

    m_pvWorkerThread = new QThread();
    	m_pvAgilentWorker = new CAgilentLan(settings_l.strfuGetIP_DSO(), 500, settings_l.bofuGetSimulate());
    	m_pvAgilentWorker->moveToThread(m_pvWorkerThread);
    	connect(m_pvWorkerThread, &QThread::started, m_pvAgilentWorker, &CAgilentLan::agilentStart);
    	connect(m_pvAgilentWorker, &CAgilentLan::finished, m_pvWorkerThread, &QThread::quit);
    
    	connect(m_pvWorkerThread, &QThread::finished, m_pvWorkerThread, &QThread::deleteLater);
    	connect(m_pvWorkerThread, &QThread::finished, m_pvWorkerThread, &CAgilentLan::deleteLater);
    	m_pvWorkerThread->start();
    

    und genau hier liegt der Fehler.



  • Was soll die folgende Zeile machen???

    connect(m_pvAgilentWorker, &CAgilentLan::finished, m_pvWorkerThread, &QThread::quit);
    


  • wenn des Signals der Object CAgilentLan aufgerufen wird. dann muss der Thread zu ende sein.



  • QThread::quit() beendet die Eventloop in deinem Thread, falls vorhanden. QThread::terminate() kann threads wohl beenden. Wann das genau passiert ist wohl System abhängig
    Aber willst du das wirklich? Der Agilenten ist an den Thread gekoppelt und führt die Aktion aus der Klasse aus, ich denke nicht, dass das der Weg ist, wie das funktionieren kann.

    Außerdem glaube ich nicht, dass dein Agilenten Objekt das Signal emited, oder Emittenten sollte.

    Kleiner Tipp: Bau dir erstmal in einer Sandbox ein funktionierendes Beispiel mit einem QThread. Und versuche das dann auf deinen Agilenten zu übertragen.



  • Hallo zusammen,

    @Schlangenmensch

    Problem habe ich mittlerweile gelöst.
    Es reicht eigentlich vollkommen, wenn ich Signal/Slot verwende.
    Danke noch mal.



  • Ich nutze einfach mal den Thread hier weil ich ebenfalls ein Problem mit QThread habe.

    Ich nutze QThread folgendermaßen, um aufwändige Arbeit zu erledigen:

    TimeSeriesManager::TimeSeriesManager( QObject *parent ) :
        QObject( parent )
    {
        auto worker = new TimeSeriesLoader();
        worker->moveToThread( &thread );
    
        assert( QObject::connect( this, SIGNAL( sigLoadTimeSeries(std::shared_ptr<TimeSeries>)), worker, SLOT(onLoadTimeSeries(std::shared_ptr<TimeSeries>)) ) );
        assert( QObject::connect( worker, SIGNAL(sigTimeSeriesLoaded(std::shared_ptr<TimeSeries>)), this, SLOT(onTimeSeriesLoaded(std::shared_ptr<TimeSeries>)) ) );
        assert( QObject::connect( &thread, SIGNAL(finished()), worker, SLOT(deleteLater())));
        thread.start();
    }
    
    TimeSeriesManager::~TimeSeriesManager()
    {
        thread.quit();
        thread.wait();
    }
    

    D.h. der Thread läuft dauernd und bekommt über SIGNAL/SLOT seine Arbeit zugewiesen. Beim Start der Applikation bekommt der Thread so an die 2000 Jobs übertragen.

    Nun will ich die Anwendung beenden. Das Problem ist, dass der Thread trotz Aufruf von "quit()" erst noch seine Eventloop abarbeitet, d.h. die Anwendung endet erst, wenn der Thread alle seine Jobs abgearbeitet hat, was reichlich bescheuert ist.

    Gibt es eine Möglichkeit den Thread zu überzeugen, die Eventloop zu leeren?



  • Laut source nicht.

    Das ist recht spannend...der eventDispatcher scheint selbst einen QAbstractEventDispatcher::interrupt() zu "ignorieren"...

    Dir wird wohl nichts anderes uebrig bleiben als deinen eigenen QAbstractEventDispatcher zu schreiben.

    Edit: Hast du es bereits mit QThread::setTerminationEnabled und QThread::terminate probiert?



  • QThread::terminate gibt aber keine Garantie, wann und wo der Thread beendet wird. Könnte also mitten in einer Schreiboperation passieren, was zu einem inkonsistenten Zustand führen könnte.

    Wenn ich It0101 richtig verstehe, soll der Thread auch ruhig seinen aktuellen Job noch abarbeiten, nur die anderen 99, die noch in der Queue sind, nicht mehr, oder?



  • Schlangenmensch schrieb:

    Wenn ich It0101 richtig verstehe, soll der Thread auch ruhig seinen aktuellen Job noch abarbeiten, nur die anderen 99, die noch in der Queue sind, nicht mehr, oder?

    Genau das.
    Ich könnte auch das "wait" einfach weglassen und den Thread dann sterben lassen, während er noch läuft. Beim ShutDown der Applikation wäre das ja halb so schlimm.

    Aber ich will es halt möglichst ordentlich beenden. Daher das wait(). Leider wartet er mir etwas zu lange 😉


Anmelden zum Antworten