So I have a simple Qt4 application with a Start button, a Stop button, and a text field. When the Start button is pressed, a new thread is spawned that continually increments a counter (in a while loop), and the text field is updated to reflect this via signals/slots. When the Stop button is pressed, the counting stops, until the Start button is pressed again.
It works ... sort of. It only updates the counter once per second; I want it to go faster, say 44100 times per second. Taking out the sleep(1) call, however, causes the while loop to block, no GUI events are recognized, and the application freezes. Also, the Stop button only works on the second try?
This used to work pretty much fine when I was subclassing QThread, but I was told not to do that, so I tried making a solution with subclassing QObject and then moving the object to a QThread ... but it isn't working as well as it used to.
Here's the code:
Worker.h
class Worker : public QObject
{
Q_OBJECT
public:
Worker();
public slots:
void process();
void stopRunning();
signals:
void signalValueUpdated(QString);
private:
bool running;
};
Worker.cpp
#include "Worker.h"
void Worker::process()
{
qDebug("Worker thread id %d",(int)QThread::currentThreadId());
static int value=0;
running = 1;
while(running == 1)
{
QString string = QString("value: %1").arg(value++);
sleep(1); //I want to take this out or make it way shorter but I can't without it crashing.
emit signalValueUpdated(string);
QCoreApplication::processEvents();
}
}
void Worker::stopRunning()
{
qDebug("stopRunning() invoked");
running = 0;
}
MainWindow.h
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
private:
//Widgets
QHBoxLayout * boxLayout;
QPushButton * startButton;
QPushButton * stopButton;
QLineEdit * lineEdit;
//Thread where the worker lives
QThread * workerThread;
//Worker object itself
Worker * worker;
};
MainWindow.cpp
#include "MainWindow.h"
MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
{
boxLayout = new QHBoxLayout(this);
startButton = new QPushButton("Start Counter", this);
stopButton = new QPushButton("Stop Counter", this);
lineEdit = new QLineEdit(this);
boxLayout->addWidget(startButton);
boxLayout->addWidget(stopButton);
boxLayout->addWidget(lineEdit);
qDebug("Thread id %d",(int)QThread::currentThreadId());
workerThread = new QThread;
worker = new Worker();
worker->moveToThread(workerThread);
connect( startButton, SIGNAL(clicked()), workerThread, SLOT(start()), Qt::QueuedConnection ); //When the start button is clicked, start the worker thread
connect( startButton, SIGNAL(clicked()), worker, SLOT(process()), Qt::QueuedConnection ); //When the start button is clicked, start the worker thread
connect( workerThread, SIGNAL(started()), worker, SLOT(process()), Qt::QueuedConnection ); //When the worker thread starts, begin the Worker object's process function
connect( stopButton, SIGNAL(clicked()), worker, SLOT(stopRunning()), Qt::QueuedConnection ); //When the stop button is clicked, invoke the Worker's stopRunning()
connect( worker, SIGNAL(signalValueUpdated(const QString&)), lineEdit, SLOT(setText(const QString&)), Qt::QueuedConnection ); //When the Worker emits a signalValueChanged, update the lineEdit to reflect this
}
Without sleep(1) and processEvents() in Worker.cpp, the whole thing crashes, but with them it is slowed down too much, only updating the number once per second instead of 1000 or more. How can I make the while(running) loop not block?
Thanks in advance! Still trying to wrap my head around the best way to do multithreading in Qt.
Remove QCoreApplication::processEvents()
in the worker, if you don't need it (why do you need it? GUI events should be already processed by the main thread...). That may be the cause of your problem.
Connect thread signals in the right way:
connect(workerThread, SIGNAL(started()), worker, SLOT(process()));
connect(startButton, SIGNAL(clicked()), thread, SLOT(start()));
(remove the connect of startButton.clicked()
->worker.process()
- it is useless and does not do what it is written in the comment)
Because connecting the button directly to the process in the worker is not right. You should connect the button to the start of the Thread, then the Thread (with started) to the worker process()
. Avoiding direct connections should avoid the GUI freeze problems you are facing.
Also, you need to create a new thread and "fire" it every time you click on the button. You may do it by wiring the button to a SLOT
in the window, and create the thread from it (creating it in the MainWindow
constructor will not work).
Taken from a (working) program I have:
// You need another slot in MainWindow, let it be "startProcessing()"
// in MainWindow::MainWindow, connect the start button to startProcessing()
connect(btnStart, signal(clicked(), this, SLOT(startProcessing())
// inside the startProcessing slot
void MainWindow::startProcessing() {
...
Worker* worker = new Worker;
QThread* thread = new QThread;
// start the work
worker->moveToThread(thread);
connect(thread, SIGNAL(started()), worker, SLOT(process()));
// Take care of cleaning up when finished too
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
As you may have noticed, I added code to clean it up as well (deleteLater()
).
The stop button will also give you some problems, but you should have now a good idea on how to proceed.