2017-01-11 5 views
3

Ich habe eine Arbeiterklasse, die Bilderfassung im Hintergrund machen.Richtig beenden QThread

void acq::run() 
{ 
    while (m_started) 
    { 
     blocking_call(); 
    } 
    emit workFinished(); 
} 

void acq::start() 
{ 
    m_started = true; 
    run(); 
} 

void acq::stop() 
{ 
    m_started = false; 
} 

start(); stop() sind Steckplätze und workFinished ist ein Signal.

Also in meinem UI-Klasse, ich den Arbeiter zu starten und ich verbinden die Signale an die Steckplätze:

m_thread = new QThread; 
m_worker = new acq(); 

m_worker->moveToThread(m_thread); 

// When the thread starts, then start the acquisition. 
connect(m_thread, SIGNAL (started()), m_worker, SLOT (start())); 

// When the worker has finished, then close the thread 
connect(m_worker, SIGNAL(workFinished()), m_thread, SLOT(quit())); 

m_thread->start(); 

An diesem Punkt setzte ich den Schlitz, closeEvent

void UIClass::closeEvent (QCloseEvent *event) 
{ 
    m_worker->stop(); // tell the worker to close 
    m_thread->wait(); // wait until the m_thread.quit() function is called 
    event->accept(); // quit the window 
} 

Unfortanely, m_thread->wait() blockiert. Selbst wenn das Signal quit() wird

ausgestrahlte

Dank

edit:

Ich habe diese beiden Anschlüsse:

connect(m_worker, SIGNAL(workFinished()), m_worker, SLOT(deleteLater())); 
connect(m_thread, SIGNAL(finished()), m_thread, SLOT(deleteLater())); 

und ein qDebug in acq::~acq()

Die Nachricht wird gedruckt, das beweisen , dieser Stopp wird aufgerufen, workFinished wird ausgegeben, deleteLater() wird ausgegeben.

+0

Haben Sie Ihren Debugger verwendet, um sicherzustellen, dass der Thread, auf den Sie warten, tatsächlich beendet wurde - und wenn er nicht beendet wird, was er tut? – UKMonkey

Antwort

0

Mit Hilfe von Ralph Tandetzky und Kevin Krammer habe ich endlich eine Lösung gefunden.

  • Statt den Faden mit m_worker->stop(); des Schließens, verwende ich QMetaObject::invokeMethod(m_worker, "stop", Qt::ConnectionType::QueuedConnection); und QCoreApplication::processEvents(); in dem Arbeiter Ereignisschleife. Das Verhalten ändert sich nicht, aber ich hoffe, dass es Race Condition oder andere Probleme verhindert.

  • Anstelle von: connect(m_worker, SIGNAL(workFinished()), m_thread, SLOT(quit()));, verwende ich eine benutzerdefinierte Slot:

    connect(m_worker, &Acq::workFinished, [=] 
    { 
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); 
        QMetaObject::invokeMethod(m_thread, "quit", Qt::ConnectionType::DirectConnection); 
    }); 
    

    Wir verwenden Direct weil wir außerhalb der Endlosschleife sind, so dass die Veranstaltung nicht verarbeitet.

  • Damit hatte ich ein letztes Problem. m_thread->wait blockiert, und ich muss Ereignisse lesen, sonst wird mein benutzerdefinierter Slot nie aufgerufen. So habe ich meiner UI-Klasse QEventLoop m_loop eine Ereignisschleife hinzugefügt.
    Kurz vor m_thread->wait() schrieb ich m_loop.exec(); Und schließlich, In meinem Kundengobo, lege ich m_loop.quit()

    connect(m_worker, &Acq::workFinished, [=] 
    { 
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); 
        QMetaObject::invokeMethod(m_thread, "quit", Qt::ConnectionType::DirectConnection); 
        m_loop.quit(); 
    }); 
    

    m_loop.exec() Ereignisprozess bis beenden m_loop.quit() aufgerufen wird. Mit dieser Methode brauche ich nicht einmal m_thread->wait(), weil m_loop.quit() aufgerufen wird, wenn workFinished ausgegeben wird. Ich brauche nicht mehr QMetaObject::invokeMethod(m_thread, "quit", Qt::ConnectionType::DirectConnection);

Jetzt funktioniert es wie ein Zauber

EDIT: Diese Lösung ist sehr schwer und hässlich, Qt (https://www.qtdeveloperdays.com/sites/default/files/David%20Johnson%20qthreads.pdf) sugest Unterklasse und requestInteruption in meinem Fall zu verwenden.

3

Eine normale Signal/Slot-Verbindung zwischen Objekten auf verschiedenen Threads erfordert, dass der Thread des Empfängerobjekts eine Ereignisschleife ausführt.

Ihr Empfänger-Thread führt theoretisch seine Ereignisschleife aus, aber die Ereignisschleife ist mit der Ausführung des start()-Steckplatzes beschäftigt, da run() niemals zurückgegeben wird.

Sie müssen entweder die Empfängerereignisschleife entsperren oder den Stoppsteckplatz mit einer Qt::DirectConnection aufrufen.

Wenn Sie letzteres tun, müssen Sie beachten, dass der Slot jetzt im Kontext des Sender-Threads aufgerufen wird und Sie m_started gegen gleichzeitigen Zugriff schützen müssen.

Alternativ Ihre eigene Flagge zu verwenden könnten Sie QThread::requestInterruption() und QThread::isInterruptionRequested()

+0

Danke für die Antwort. Ich habe einige Fragen: - Eine normale Signal/Slot-Verbindung zwischen Objekten auf verschiedenen Threads erfordert, dass der Thread des Empfängerobjekts eine Ereignisschleife ausführt. Empfänger ist der Woker richtig? - aber die Ereignisschleife ist damit beschäftigt, den start() -Slot auszuführen, da run() niemals zurückkehrt. Warum wird der qDebug in der Funktion stop() aufgerufen, wenn der Worker beschäftigt ist? Hast du ein Beispiel? – Epitouille

+0

@Epitouille, wird 'stop()' slot in Ihrem Fall aufgerufen? – Mike

+0

@Mike Ja, es ist – Epitouille

1

QCoreApplication::processEvents(); 

in Ihre Loop hinzufügen und es wird funktionieren.

Der Grund für Deadlock ist, dass der Aufruf an acq::run() blockiert und keine Zeit für acq::stop() auf dem Worker-Thread ausgeführt wird.

+0

Danke für die Antwort. Ich denke nicht, QCoreApplication :: processEvents (QEventLoop :: WaitForMoreEvents); ist die Lösung. Das Hinzufügen dieser Zeile in der Schleife "acq :: run()" blockiert meinen Thread. Der Arbeiter steckt in dieser Linie fest. Allerdings wird acq :: stop() aufgerufen, weil mein qDebug() in dieser Funktion (Slot) erscheint. – Epitouille

+0

@Epitouille Haben Sie auch 'QObject :: den' acq :: stop() 'Slot aufgerufen? –

+0

Ja, ich habe m_worker-> stop() in QMetaObject :: invokeMethod geändert (m_worker, "stop", Qt :: ConnectionType :: DirectConnection); – Epitouille

Verwandte Themen