Qt Threading - How to manage a background process
-
Hi,
After spending many, many hours searching around c++ threading, Qt threading etc, I have come here to ask my question...
I am trying to make a program that has a button that scans the current file system recursively. To prevent hanging the GUI, and to allow the user to cancel the operation once started, I have moved the process to a QObject class which is moved to a thread.
The slots/signals pattern is used to launch the thread execution, however I am unable to stop the thread. Once the thread has been started, the GUI continuously counts up, but never receives the signal to stop, and does not set _shouldRun to false. Can someone please tell me what I am doing wrong with the code below?
Thanks in advance for any and all advice.
backupController.cpp
@
#include "backupController.h"
#include "backupCalculator.h"
#include <QThread>
#include <QDebug>backupController::backupController() {
_count = 0;
}backupController::~backupController() {
}
bool backupController::startCalculate()
{qDebug("Starting thread"); thread = new QThread; backupCalculator* bCalc = new backupCalculator(); bCalc->moveToThread(thread); // connect the thread start signals connect(thread, SIGNAL(started()), bCalc, SLOT(startCalculate())); //connect the thread stop signals connect(this, SIGNAL(stopCalculating()), bCalc, SLOT(stopCalculate())); // connect the update SIGNAL from the worker to the controller SIGNAL connect(bCalc, SIGNAL(statusUpdate()), this, SLOT(notifyUpdated())); thread->start(); return true;
}
bool backupController::stopCalculate() {
qDebug("Clicked Stop Calculating");
emit stopCalculating();//thread->quit(); //thread->terminate(); //thread->exit(); qDebug("Quitting thread"); return true;
}
void backupController::notifyUpdated() {
//qDebug("sending update to master");
qDebug("count = %d", _count);
_count++;
emit notifyUpdate();
emit countChanged(); // notify QML that the variable has been updated.. yay works!! :)
}
@backupCalculator.cpp
@
#include <QString>
#include <QDateTime>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include "backupCalculator.h"void backupCalculator::startCalculate(){
//...
qDebug("Called StartCalculate");
_shouldRun = true;while (_shouldRun) { QMutex dummy; dummy.lock(); QWaitCondition waitCondition; qDebug("...sleeping"); waitCondition.wait(&dummy, 1000); //QThread::sleep(1); emit statusUpdate(); qDebug("shouldRun = %d", _shouldRun); } emit statusUpdate();
}
void backupCalculator::stopCalculate(){
//...
qDebug("Called StopCalculate");
_shouldRun = false;
qDebug("shouldRun after update = %d", _shouldRun);
//emit statusUpdate("_shouldRun");
}
@Which results in the following debug output:
@
QML debugging is enabled. Only use this in a safe environment.
Starting thread
Called StartCalculate
...sleeping
shouldRun = 1
QMutex: destroying locked mutex
...sleeping
count = 0
shouldRun = 1
QMutex: destroying locked mutex
...sleeping
count = 1
Clicked Stop Calculating
Quitting thread
shouldRun = 1
QMutex: destroying locked mutex
...sleeping
count = 2
shouldRun = 1
QMutex: destroying locked mutex
...sleeping
count = 3
Clicked Stop Calculating
Quitting thread
shouldRun = 1
QMutex: destroying locked mutex
...sleeping
count = 4
shouldRun = 1
QMutex: destroying locked mutex
...sleeping
count = 5
Clicked Stop Calculating
Quitting thread
shouldRun = 1
QMutex: destroying locked mutex
...sleeping
count = 6
Clicked Stop Calculating
Quitting thread
shouldRun = 1
QMutex: destroying locked mutex
...sleeping
count = 7
Clicked Stop Calculating
Quitting thread
shouldRun = 1
QMutex: destroying locked mutex
...sleeping
count = 8
Clicked Stop Calculating
Quitting thread
shouldRun = 1
QMutex: destroying locked mutex
...sleeping
count = 9
Clicked Stop Calculating
Quitting thread
Clicked Stop Calculating
Quitting thread
shouldRun = 1
QMutex: destroying locked mutex
...sleeping
count = 10
@ -
Hmm,
Your general idea is according all the books I read perfect in order. Don't think it has to do with the general idea. It's probably got to do with a "misuse" of a signal/slot somewhere. The output application in QtCreator in debugger mode will give information when a signal/slot is not properly connected. An other idea is to use the "new" Qt5 way of filling out signal/slots:
@connect(this, &<signalname>, otherclass, &slotname); @
The big big advantage is better syntax checking so your program is not compilable when the signal/slot could not be connected.
Also, the connect function returns a bool. If false, the connection could not be made.The second thing is that in your backupCalculator::startCalculate() function you define the QWaitCondition to be BlockScope as well as your mutex. Keep in mind that both variables are destroyed every time your while loop has run ones!! Not when your thread is killed. This might cause some problems with your thread synchronization.
-
@void backupCalculator::startCalculate(){
QMutex dummy;
QWaitCondition waitCondition;
//...
qDebug("Called StartCalculate");
_shouldRun = true;while (_shouldRun) { dummy.lock(); qDebug("...sleeping"); waitCondition.wait(&dummy, 1000); //QThread::sleep(1); emit statusUpdate(); qDebug("shouldRun = %d", _shouldRun); } emit statusUpdate();
}@
This might do the trick. Make them function scope and within the while you're ok to use them. -
Hi,
One of the main problem is your use of QWaitCondition and QMutex. You never wake your wait condition and currently you cannot because it's scoped to the while loop. The same idea applies to your mutex, it also only exists for the one loop duration, thus you won't protect anything and lock your thread immediately.
Depending on the operation you want to do, you might want to check QtConcurrent, could be a lot simpler to your use case.
-
So, thanks to you both for your explanations around the Mutex and WaitCondition. This has helped my understanding of how these are used, and has will be very useful as this code is implemented... :)
However, it didn't obtain the results that I expected, so continued to investigate.
Finally, I came to the conclusion that whilst the thread was operating on a function, it was not possible to send a second event to the thread for processing, as how would it be able to process the event if it was already running the previous function? (if all that makes sense)
I changed the boolean to a pointer to the parent class, and removed the second emit. Now the threaded function uses the reference to the boolean, and I can control the execution of the thread from the master thread.
Here is the solution I have:
backupController.cpp
@
#include "backupController.h"
#include "backupCalculator.h"
#include <QThread>
#include <QDebug>#include "counter.h"
backupController::backupController() {
_count = 0;
_executeLoop = false;
thread = new QThread;
backupCalculator* bCalc = new backupCalculator();
bCalc->moveToThread(thread);// connect the thread start signals connect(this, SIGNAL(startCalculating(bool *)), bCalc, SLOT(startCalculate(bool *))); //connect the thread stop signals connect(this, SIGNAL(stopCalculating()), bCalc, SLOT(stopCalculate())); //connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); // connect the update SIGNAL from the worker to the controller SIGNAL connect(bCalc, SIGNAL(statusUpdate()), this, SLOT(notifyUpdated())); thread->start();
}
backupController::~backupController() {
}
void backupController::startCalculate()
{qDebug("Signalling startCalculate()"); _executeLoop = true; emit startCalculating(&_executeLoop); qDebug("Finished Controller Startup");
}
void backupController::notifyUpdated() {
qDebug("count = %d", _count); _count++; emit notifyUpdate(); emit countChanged(); // notify QML that the variable has been updated.. yay :)
}
void backupController::stopCalculate() {
qDebug("stopping Calculation");_executeLoop = false;
}
@backupCalculator.cpp
@
#include <QString>
#include <QDateTime>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include "backupCalculator.h"backupCalculator::backupCalculator(){
}
backupCalculator::~backupCalculator(){
}
void backupCalculator::startCalculate(bool *_shouldLoop){
qDebug("Called StartCalculate"); while (*_shouldLoop) { qDebug("...sleeping"); QThread::sleep(1); emit statusUpdate(); qDebug("shouldRun = %d", *_shouldLoop); }
}
@ -
Please for next time include the whole of your code as unable to use this as an example because you only detail the .cpp files. Which makes it very hard for anyone to learn from your mistakes.