Pass value by reference to a thread and modify - c++

I want to modify a Qlist by reference in a thread because the sorting could take a few seconds.
But it seems like that I can't modify the List. Outside of the thread the List has not beend changed.
In QMainwindow:
QThread *thread = new QThread();
FarmSortWorker *farmsort_worker = new FarmSortWorker();
farmsort_worker->setFarmData(farm_list);
farmsort_worker->moveToThread(thread);
connect(farmsort_worker, &FarmSortWorker::error, [=](QString error_msg){
qDebug() << error_msg;
logger->logEvent(error_msg, Logger::ERROR);
});
connect(thread, &QThread::started, farmsort_worker, &FarmSortWorker::processSort);
connect(farmsort_worker, &FarmSortWorker::finished, thread, &QThread::quit);
connect(farmsort_worker, &FarmSortWorker::finished, farmsort_worker, &QThread::deleteLater);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
My Thread (.h)
#ifndef FARMSORTWORKER_H
#define FARMSORTWORKER_H
#include <QObject>
#include "../framcontainer.h"
#include <QList>
#include <qDebug>
#include <QString>
class FarmSortWorker : public QObject
{
Q_OBJECT
public:
explicit FarmSortWorker(QObject *parent = 0);
~FarmSortWorker();
void setFarmData(QList<FramContainer> &farm_container);
private:
QList<FramContainer> farm_container;
signals:
void error(QString error);
void finished();
public slots:
void processSort();
};
#endif // FARMSORTWORKER_H
My Thread (.cpp)
#include "farmsortworker.h"
FarmSortWorker::FarmSortWorker(QObject *parent) :
QObject(parent)
{
}
FarmSortWorker::~FarmSortWorker()
{
}
void FarmSortWorker::setFarmData(QList<FramContainer> &farm_container)
{
this->farm_container = farm_container;
}
void FarmSortWorker::processSort()
{
qDebug() << "count:" << farm_container.size();
for(int i = 0; i < farm_container.size(); i++)
{
FramContainer park = farm_container.at(i);
qDebug() << "original:" << park.getFarmName();
}
for(int i = 0; i < farm_container.size(); i++)
{
FramContainer *park =& farm_container[i];
park->setFarmName("test");
}
for(int i = 0; i < farm_container.size(); i++)
{
FramContainer park = farm_container.at(i);
qDebug() << "neu:" << park.getFarmName();
}
emit finished();
}
Thank you all in advance.

It's because you save a copy of the list in your thread class, not a reference.
Instead of having a separate function to set the list, pass it (by reference) to the constructor, and have it set the reference variable:
class FarmSortWorker : public QObject
{
Q_OBJECT
public:
explicit FarmSortWorker(QList<FramContainer>& list, QObject *parent = 0);
...
private:
QList<FramContainer>& farm_container; // Store as a reference
...
};
...
FarmSortWorker::FarmSortWorker(QList<FramContainer>& list, QObject *parent) :
QObject(parent), farm_container(list)
{
}
...
QThread *thread = new QThread();
FarmSortWorker *farmsort_worker = new FarmSortWorker(farm_list);
...

Related

GUI lag issue when using thread

This is the code i'm using now the issue is when i press on the pushbutton
the thread is starting and the value in line edit is updating.
but it slows down the GUI overall.
I am learning QThread so implemented this code and facing difficulties in it.
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
qDebug()<<"pd1";
work->moveToThread(thread);
connect(work, SIGNAL(finished()), work, SLOT(deleteLater()));
connect(thread, SIGNAL(started()), work, SLOT(process()));
connect(work, SIGNAL(datar(int)), this, SLOT(display(int)));
connect(work, SIGNAL(finished()), thread, SLOT(quit()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
qDebug()<<"pd2";
}
void MainWindow::display(int i)
{
ui->lineEdit->setText(QString::number(i));
}
void MainWindow::on_pushButton_2_clicked()
{
qDebug()<<"In push button - 2";
for(int i = 0; i < 200; i++)
{
qDebug()<<i;
ui->lineEdit_2->setText(QString::number(i));
}
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <worker.h>
#include <QThread>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void display(int i);
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
void on_pushButton_3_clicked();
void on_pushButton_4_clicked();
private:
Ui::MainWindow *ui;
worker* work = new worker();
QThread* thread = new QThread;
};
worker.cpp
#include "worker.h"
#include <QDebug>
worker::worker(QObject *parent) : QObject(parent)
{
}
void worker::process()
{
int index = 0;
qDebug()<<"In here";
while(true)
{
qDebug("Hello World!");
index += 1;
if(index > 10000)
{
index = 0;
}
emit datar(index);
}
emit finished();
}
worker.h
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
class worker : public QObject
{
Q_OBJECT
public:
explicit worker(QObject *parent = 0);
signals:
void finished();
void datar(int);
public slots:
void process();
};
#endif // WORKER_H
What i wanted was to update the line edit continusoly from thread such that it doesn't affect the GUI performance.
It would be great if you identify the mistake and suggest me the changes to do.
Consider your worker::process implementation...
void worker::process()
{
int index = 0;
qDebug()<<"In here";
while(true)
{
qDebug("Hello World!");
index += 1;
if(index > 10000)
{
index = 0;
}
emit datar(index);
}
emit finished();
}
It emits the datar signal continuously and without any intervening delays. But the signal emitter and receiver are on different threads meaning the signal will be delivered to the receiver via its event queue. So you are basically saturating the GUI thread's event loop with events from the datar signal.
Try putting even a slight delay between signals with something like...
QThread::msleep(10);

How to override a signal in qt?

I have a question how to override the signal in Qt?
I have redefined QCheckBox() and changed stateChanged signal.
The project is bilding and working. It does not output in the "application output" errors or messages of the " signal with the slot is not connected"
BUT it doesn't link to the slot. I can't figure out what's wrong.
This code works :
connect(test_checkbox[i], SIGNAL(stateChanged(int)), two_cl , SLOT(run_NEW()));
I need to emit string in addition to the number :
connect(test_checkbox[i], SIGNAL(stateChanged(int, QString)), two_cl , SLOT(run_NEW(int, QString)));
override QCheckBox
.h
#ifndef MYDIMASCHECKBOX_H
#define MYDIMASCHECKBOX_H
#include <QCheckBox>
class MyDimasCheckBox : public QCheckBox
{
Q_OBJECT
public:
MyDimasCheckBox(QWidget *parent =0);
~MyDimasCheckBox();
QString stroka;
signals:
void stateChanged(int, QString);
};
#endif // MYDIMASCHECKBOX_H
.cpp
#include "mydimascheckbox.h"
MyDimasCheckBox::MyDimasCheckBox(QWidget *parent)
{
stroka = "dimasik :3";
emit stateChanged(int(), stroka);
}
MyDimasCheckBox::~MyDimasCheckBox()
{
}
And here is where challenge
.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QDebug>
#include <QThread>
#include <QCoreApplication>
#include <iostream>
#include <vector>
#include "mydimascheckbox.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
int glob_i ;
int glob_flow;
int vector_schet;
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void start_sending(bool);
private slots:
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
QThread *thread = new QThread();
QVector<QThread*> vector_thread;
QList<MyDimasCheckBox*> test_checkbox;
MyDimasCheckBox *checkBox = new MyDimasCheckBox();
QWidget *checkBoxWidget = new QWidget();
QHBoxLayout *layoutCheckBox = new QHBoxLayout(checkBoxWidget);
};
class NewsThread: public QThread
{
Q_OBJECT
public slots:
void run_NEW();
void run_NEW(int, QString);
signals:
void otprawka (int);
};
#endif // MAINWINDOW_H
.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
glob_i=0;
glob_flow =0;
vector_schet =0;
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
test_checkbox.clear();
}
void MainWindow::on_pushButton_clicked()
{
glob_i++;
checkBoxWidget = new QWidget();
checkBox = new MyDimasCheckBox();
layoutCheckBox = new QHBoxLayout(checkBoxWidget);
test_checkbox.append(checkBox);
connect(checkBox, SIGNAL(toggled(bool)),this, SLOT(start_sending(bool)));
checkBox->setText(QString::number(glob_i));
layoutCheckBox->addWidget(checkBox);
layoutCheckBox->setAlignment(Qt::AlignCenter);
layoutCheckBox->setContentsMargins(0,0,0,0);
ui->tW_test->insertRow(ui->tW_test->rowCount());
ui->tW_test->setCellWidget(ui->tW_test->rowCount()-1, 1, checkBoxWidget);
qDebug() << "glob_i: " << glob_i;
}
void MainWindow::start_sending(bool Value)
{
qDebug() << "start_sending " ;
// когда нажата отрабатывает, отжата то не отрабатывает
if (Value == true)
{
NewsThread *two_cl = new NewsThread();
qDebug() << "chekbocks: "<< " TRUE" ;
for (int i =0;i < test_checkbox.length();i++ )
{
if(test_checkbox[i]->isChecked() ==Value)
{
glob_flow++;
// connect(test_checkbox[i], SIGNAL(stateChanged(int)), two_cl , SLOT(run_NEW()));
connect(test_checkbox[i], SIGNAL(stateChanged(int, QString)), two_cl , SLOT(run_NEW(int, QString)));
thread = new QThread();
vector_thread.append(thread);
vector_schet++;
qDebug() << "vector_schet : " << vector_schet ;
two_cl->moveToThread(vector_thread[vector_schet-1]);
vector_thread[vector_schet-1]->start();
}
}
}
else {
qDebug() << "chekbocks:" << " False";
glob_flow--;
qDebug() << "vector_schet : " << vector_schet ;
vector_thread[vector_schet-1]->exit();
}
}
void NewsThread::run_NEW()
{
qDebug() << "run_NEW()";
for(;;){
for (int i=0; i<500; i++){
qDebug()<< "Число :" << i <<"number \"flow\" :" ;
usleep(100000);
}
}
}
void NewsThread::run_NEW(int i, QString str){
qDebug() << "run_NEW(int i, QString str) ";
for(;;){
for (int i=0; i<500; i++){
qDebug() << " i : " << i;
qDebug() << " str : " << str;
qDebug()<< "Число :" << i <<"number \"flow\" :" ;
usleep(100000);
}
}
}
You cannot replace a signal with another signal in a subclass. You can, however, emit an additional signal with the original signal in a self-connected slot:
class MyDimasCheckBox : public QCheckBox
{
Q_OBJECT
public:
MyDimasCheckBox(QWidget *parent =0);
~MyDimasCheckBox();
QString stroka;
private slots:
// Emits the new signal
void doEmitStateChanged(int i);
signals:
void stateChanged(int, QString);
};
MyDimasCheckBox::MyDimasCheckBox(QWidget *parent) : QCheckBox(parent) {
// Connect original signal to slot
connect(this, SIGNAL(stateChanged(int)), this, SLOT(doEmitStateChanged(int)));
}
void MyDimasCheckBox::doEmitStateChanged(int i) {
emit stateChanged(i, stroka);
}
With the new connection syntax, you can omit the slot and use a lambda:
connect(this, qOverload<int>(&QCheckBox::stateChanged),
// "this" context-object for QThread-Affinity
this, [=](int i) { emit this->stateChanged(i, this->stroka); });
Overriding signal is a bad practice [1]:
"APIs with overridden signals are hard to use, unexpected and bug-prone. To make it worse, Qt even allows you to override a signal with a non-signal, and vice-versa."
https://www.kdab.com/nailing-13-signal-slot-mistakes-clazy-1-3/

Redirecting std::cout from DLL in a separate thread to QTextEdit

The goal is to display all the application output shown in QtCreator to a QTextEdit as a debug console window so with the same application only those who have password are allowed to see the console window while normal users cannot. There is an exe with several dlls. All std::cout from DLLs and qDebug are needed to be shown in the debug console window.
To achieve this, I have followed
http://www.qtforum.org/article/39768/redirecting-std-cout-std-cerf-qdebug-to-qtextedit.html
The code works great for single thread but is hanged when a thread is started to call functions in DLL. I would like to know how to fix the problem.
code
In mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QObject>
#include <QThread>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_qd = new Q_DebugStream(std::cout,ui->textEdit); //Redirect Console output to QTextEdit
m_qd->Q_DebugStream::registerQDebugMessageHandler(); //Redirect qDebug() output to QTextEdit
connect(ui->pushButtonSingleThreadTest, SIGNAL(clicked()),this,SLOT(SingleThreadTest()));
connect(ui->pushButtonMultiThreadTest, SIGNAL(clicked()),this,SLOT(MultiThreadTest()));
}
void MainWindow::SingleThreadTest()
{
run();
}
void MainWindow::MultiThreadTest()
{
QThread *workerThread= new QThread;
ThreadWorker *worker = new ThreadWorker();
worker->moveToThread(workerThread);
connect(workerThread, SIGNAL(started()), worker, SLOT(doWork()));
connect(worker, SIGNAL(finished()), workerThread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
workerThread->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
In mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "q_debugstream.h"
#include "../ToyProj1/Header.h"
namespace Ui {
class MainWindow;
}
class ThreadWorker : public QObject
{
Q_OBJECT
public:
ThreadWorker()
{
}
private:
signals:
void finished();
public slots:
void doWork()
{
run();
emit finished();
}
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void SingleThreadTest();
void MultiThreadTest();
private:
Ui::MainWindow *ui;
Q_DebugStream* m_qd;
};
#endif // MAINWINDOW_H
in q_debugstream.h
#ifndef Q_DEBUGSTREAM_H
#define Q_DEBUGSTREAM_H
#include <iostream>
#include <streambuf>
#include <string>
#include "QTextEdit.h"
class Q_DebugStream : public std::basic_streambuf<char>
{
public:
Q_DebugStream(std::ostream &stream, QTextEdit* text_edit) : m_stream(stream)
{
log_window = text_edit;
m_old_buf = stream.rdbuf();
stream.rdbuf(this);
}
~Q_DebugStream()
{
m_stream.rdbuf(m_old_buf);
}
static void registerQDebugMessageHandler(){
qInstallMessageHandler(myQDebugMessageHandler);
}
private:
static void myQDebugMessageHandler(QtMsgType, const QMessageLogContext &, const QString &msg)
{
std::cout << msg.toStdString().c_str();
}
protected:
//This is called when a std::endl has been inserted into the stream
virtual int_type overflow(int_type v)
{
if (v == '\n')
{
log_window->append("");
}
return v;
}
virtual std::streamsize xsputn(const char *p, std::streamsize n)
{
QString str(p);
if(str.contains("\n")){
QStringList strSplitted = str.split("\n");
log_window->moveCursor (QTextCursor::End);
log_window->insertPlainText (strSplitted.at(0)); //Index 0 is still on the same old line
for(int i = 1; i < strSplitted.size(); i++){
log_window->append(strSplitted.at(i));
}
}else{
log_window->moveCursor (QTextCursor::End);
log_window->insertPlainText (str);
}
return n;
}
private:
std::ostream &m_stream;
std::streambuf *m_old_buf;
QTextEdit* log_window;
};
#endif // Q_DEBUGSTREAM_H
In DLL,
int run()
{
std::cout << "Hello World" << std::endl;
return 0;
}
The code sample is uploaded to github for reference. Build ToyProj1 and ToyProj1GUI when repeat the problem.
https://github.com/kuwt/ToyProject.git
With the help comments in question, this solution works well with signal and slot mechanism. std::cout and qDebug are redirected to QTextEdit.
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
// Setup QMessageCatch
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
qInstallMessageHandler(MainWindow::QMessageOutput);
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QDebug>
#include <QTextEdit>
#include "q_debugstream.h"
namespace Ui {
class MainWindow;
}
class ThreadWorker : public QObject
{
Q_OBJECT
public:
ThreadWorker()
{
}
private:
signals:
void finished();
public slots:
void doWork()
{
std::cout<< "Hello World2" <<std::endl;
qDebug() << "Hello World2q" ;
emit finished();
}
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
// QMessage
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
static void QMessageOutput(QtMsgType , const QMessageLogContext &, const QString &msg);
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
public slots:
void SingleThreadTest();
void MultiThreadTest();
private:
Ui::MainWindow *ui;
// MessageHandler for display and ThreadLogStream for redirecting cout
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
MessageHandler *msgHandler = Q_NULLPTR;
ThreadLogStream* m_qd;
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QObject>
#include <QThread>
// Catch QMessage, redirect to cout
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
void MainWindow::QMessageOutput(QtMsgType , const QMessageLogContext &, const QString &msg)
{
std::cout<<msg.toStdString().c_str()<<std::endl;
}
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// Set up ThreadLogStream, which redirect cout to signal sendLogString
// Set up MessageHandler, wgucg catch message from sendLogString and Display
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
m_qd = new ThreadLogStream(std::cout); //Redirect Console output to QTextEdit
this->msgHandler = new MessageHandler(this->ui->textEdit, this);
connect(m_qd, &ThreadLogStream::sendLogString, msgHandler, &MessageHandler::catchMessage);
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
connect(ui->pushButtonSingleThreadTest, SIGNAL(clicked()),this,SLOT(SingleThreadTest()));
connect(ui->pushButtonMultiThreadTest, SIGNAL(clicked()),this,SLOT(MultiThreadTest()));
}
void MainWindow::SingleThreadTest()
{
std::cout<< "Hello World1" <<std::endl;
qDebug() << "Hello World1q" ;
}
void MainWindow::MultiThreadTest()
{
QThread *workerThread= new QThread;
ThreadWorker *worker = new ThreadWorker();
worker->moveToThread(workerThread);
connect(workerThread, SIGNAL(started()), worker, SLOT(doWork()));
connect(worker, SIGNAL(finished()), workerThread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
workerThread->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
q_debugstream.h
#ifndef ThreadLogStream_H
#define ThreadLogStream_H
#include <iostream>
#include <streambuf>
#include <string>
#include <QScrollBar>
#include "QTextEdit"
#include "QDateTime"
// MessageHandler
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
class MessageHandler : public QObject
{
Q_OBJECT
public :
MessageHandler(QTextEdit *textEdit, QObject * parent = Q_NULLPTR) : QObject(parent), m_textEdit(textEdit){}
public slots:
void catchMessage(QString msg)
{
this->m_textEdit->append(msg);
}
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
private:
QTextEdit * m_textEdit;
};
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
class ThreadLogStream : public QObject, public std::basic_streambuf<char>
{
Q_OBJECT
public:
ThreadLogStream(std::ostream &stream, QObject * parent = Q_NULLPTR) :QObject(parent), m_stream(stream)
{
m_old_buf = stream.rdbuf();
stream.rdbuf(this);
}
~ThreadLogStream()
{
// output anything that is left
if (!m_string.empty())
{
emit sendLogString(QString::fromStdString(m_string));
}
m_stream.rdbuf(m_old_buf);
}
protected:
virtual int_type overflow(int_type v)
{
if (v == '\n')
{
emit sendLogString(QString::fromStdString(m_string));
m_string.erase(m_string.begin(), m_string.end());
}
else
m_string += v;
return v;
}
virtual std::streamsize xsputn(const char *p, std::streamsize n)
{
m_string.append(p, p + n);
long pos = 0;
while (pos != static_cast<long>(std::string::npos))
{
pos = static_cast<long>(m_string.find('\n'));
if (pos != static_cast<long>(std::string::npos))
{
std::string tmp(m_string.begin(), m_string.begin() + pos);
emit sendLogString(QString::fromStdString(tmp));
m_string.erase(m_string.begin(), m_string.begin() + pos + 1);
}
}
return n;
}
private:
std::ostream &m_stream;
std::streambuf *m_old_buf;
std::string m_string;
signals:
void sendLogString(const QString& str);
};
#endif // ThreadLogStream_H
for long transactions, you can add:
QCoreApplication::processEvents();
in the catchMessage(QString(msg) method of the class MessageHandler in q_debugstream.h, after the append() call.
This updates textEdit 'as soon as possible'.

Qt - Simple example using threads controlled by push buttons

I have been trying to get this simple example using threads activated by pushbuttons to work. It is based off of the solution in the question below:
How to implement frequent start/stop of a thread (QThread)
The main differences between the example solution above and my code below are:
I used a QWidget instead of MainWindow
I changed the name of signals for clarity
My code contains debugging information
I experimented with eliminating the signals created by worker as the didn't appear to do anything
It appears that the start/stop signals are not triggering their corresponding slots, but I am not experienced enough to troubleshoot why.
Additionally, I am unsure of the purpose of the signal:
SignalToObj_mainThreadGUI()
Is that just something that could be used and is not?
I have been trying to get this code to work for some time, so any help would be greatly appreciated.
main.cpp
#include "threadtest.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ThreadTest w;
w.show();
return a.exec();
}
threadtest.h
#include <QWidget>
#include <QThread>
#include "worker.h"
namespace Ui
{
class ThreadTest;
}
class ThreadTest : public QWidget
{
Q_OBJECT
public:
explicit ThreadTest(QWidget *parent = 0);
~ThreadTest();
signals:
void startWorkSignal();
void stopWorkSignal();
private slots:
void on_startButton_clicked();
void on_stopButton_clicked();
private:
Ui::ThreadTest *ui;
worker *myWorker;
QThread *WorkerThread;
};
threadtest.cpp
#include "threadtest.h"
#include "ui_threadtest.h"
ThreadTest::ThreadTest(QWidget *parent) :
QWidget(parent),
ui(new Ui::ThreadTest)
{
ui->setupUi(this);
myWorker = new worker;
WorkerThread = new QThread;
myWorker->moveToThread(WorkerThread);
connect(this,
SIGNAL(startWorkSignal()),
myWorker,
SLOT(StartWork())
);
connect(this,
SIGNAL(stopWorkSignal()),
myWorker,
SLOT(StopWork())
);
//Debug
this->dumpObjectInfo();
myWorker->dumpObjectInfo();
}
ThreadTest::~ThreadTest()
{
delete ui;
}
void ThreadTest::on_startButton_clicked()
{
qDebug() << "startwork signal emmitted";
emit startWorkSignal();
}
void ThreadTest::on_stopButton_clicked()
{
qDebug() << "stopwork signal emmitted";
emit stopWorkSignal();
}
worker.h
#include <QObject>
#include <QDebug>
class worker : public QObject {
Q_OBJECT
public:
explicit worker(QObject *parent = 0);
~worker();
signals:
void SignalToObj_mainThreadGUI();
//void running();
//void stopped();
public slots:
void StopWork();
void StartWork();
private slots:
void do_Work();
private:
volatile bool running, stopped;
};
worker.cpp
#include "worker.h"
worker::worker(QObject *parent) : QObject(parent), stopped(false),
running(false)
{
qDebug() << "running: " << running;
qDebug() << "stopped: " << stopped;
}
worker::~worker() {}
void worker::do_Work()
{
qDebug() << "inside do Work";
emit SignalToObj_mainThreadGUI();
if (!running || stopped) return;
// actual work here
/*
for (int i = 0; i < 100; i++)
{
qDebug() << "count: " + i;
}
*/
QMetaObject::invokeMethod(this, "do_Work", Qt::QueuedConnection);
}
void worker::StopWork()
{
qDebug() << "inside StopWork";
stopped = true;
running = false;
//emit stopped();
}
void worker::StartWork()
{
qDebug() << "inside StartWork";
stopped = false;
running = true;
//emit running();
do_Work();
}
You should write
WorkerThread->start();
Or you can use the thread of the ThreadTest object instead the WorkerThread (in this case the WorkerThread is needless):
myWorker->moveToThread(thread()); // this->thread
The slots are not triggered, because you have moved myWork to the thread WorkerThread, but didnot run an event loop in that thread. In threadtest.cpp, add
WorkerThread .start();
after
myWorker = new worker;
WorkerThread = new QThread;
myWorker->moveToThread(WorkerThread);

Doing QThread-ing the "right" way

With this program I press the 'run' button and a for loop cycles 100 times (with a 100ms delay)
and prints the cycle count in a txt field
I have successfully done it with a MyThread object derived from QThread. It works. I can
interrupt the cycle with the 'stop' button.
However it is sternly warned that deriving an object from QThread is very bad. So I did it
the other way they suggested, the "right" way.
And it doesn't work. I can get the loop cycle numbers out on the console but not into the text box
The 'run' button goes down and never comes up again until the 100 cycles are done.
And the text field shows 99.
Here is the code, what am I doing wrong?
// MyWidget.h SF022
#ifndef MYWIDGET_H_
#define MYWIDGET_H_
#include <QtWidgets/QWidget>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton>
#include "../MyThread.h"
#include "../MyObject.h"
#include <QThread>
class MyWidget : public QWidget {
Q_OBJECT
public:
MyWidget(QWidget* parent = 0);
~MyWidget();
QPushButton* pbRun;
QPushButton* pbStop;
QPushButton* pbExit;
QLineEdit* txtCount;
QThread* cThread;
MyObject* myObject;
public slots:
void onNumberChanged(int);
private slots:
void pbRun_Slot();
void pbStop_Slot();
void pbExit_Slot();
};
#endif /* MYWIDGET_H_ */
------------------------------------------------
// MyWidget.cpp
#include "MyWidget.h"
#include "../K.h"
#include <iostream>
MyWidget::MyWidget(QWidget* parent) : QWidget(parent) {
pbRun = new QPushButton(this);
pbRun->setObjectName(QStringLiteral("pbRun"));
pbRun->setGeometry(QRect(20, 20, 80, 40));
pbRun->setText("Run");
connect(pbRun, SIGNAL(clicked()), this, SLOT(pbRun_Slot()));
pbStop = new QPushButton(this);
pbStop->setObjectName(QStringLiteral("pbStop"));
pbStop->setGeometry(QRect(20, 80, 80, 40));
pbStop->setText("Stop");
connect(pbStop, SIGNAL(clicked()), this, SLOT(pbStop_Slot()));
pbExit = new QPushButton(this);
pbExit->setObjectName(QStringLiteral("pbExit"));
pbExit->setGeometry(QRect(20, 140, 80, 40));
pbExit->setText("Exit");
connect(pbExit, SIGNAL(clicked()), this, SLOT(pbExit_Slot()));
txtCount = new QLineEdit(this);
txtCount->setGeometry(QRect(20, 200, 80, 40));
txtCount->setStyleSheet("QLineEdit{background: white;}");
// myObject holds the cycling mechanism
myObject = new MyObject(this);
// the myObject sends each new cycle number out here
connect(myObject, SIGNAL(numberChanged(int)), this, SLOT(onNumberChanged(int)));
}
MyWidget::~MyWidget() {
}
void MyWidget::pbRun_Slot() {
// start thread
cThread = new QThread(this);
myObject->doSetup(*cThread);
myObject->moveToThread(cThread);
cThread->start();
}
void MyWidget::pbStop_Slot() {
// stop the thread
myObject->Stop = true;
}
void MyWidget::pbExit_Slot() {
// a static pointer to the main window
(K::SfMainWin)->close();
}
// a slot
void MyWidget::onNumberChanged(int j) {
// output the cycle count to a text field
txtCount->setText(QString::number(j));
}
----------------------------------------------------------
// MyObject.h
#ifndef MYOBJECT_H_
#define MYOBJECT_H_
#include <QObject>
#include <QThread>
class MyObject : public QObject {
Q_OBJECT
public:
explicit MyObject(QObject* parent = 0);
~MyObject();
void doSetup(QThread&);
bool Stop;
signals:
void numberChanged(int);
public slots:
void doWork();
};
#endif /* MYOBJECT_H_ */
----------------------------------------------------------
// MyObject.cpp
#include "MyObject.h"
#include <QMutex>
#include <iostream>
#include "string.h"
MyObject::MyObject(QObject* parent) : QObject(parent) {
Stop = false;
}
MyObject::~MyObject() {
}
void MyObject::doSetup(QThread& cThread) {
Stop = false;
connect(&cThread, SIGNAL(started()), this, SLOT(doWork()));
}
void MyObject::doWork() {
for (int i = 0; i < 100; i++) {
QMutex mutex;
mutex.lock();
if (this->Stop) {
break;
}
// output into a text field
emit numberChanged(i);
// output on the console
std::cout << "running " << (QString::number(i)).toStdString() << std::endl;
mutex.unlock();
QThread::msleep(100);
}
}
myObject is never moved to the thread you created. Everything is being executed in the main thread. Because
myObject = new MyObject(this);
To move a QObject to another thread, he should not have a parent. If it does Qt will silently tell you something went wrong ( by printing on the output, same with incorrect connections). It is the framework design to not panic over this type of warnings...
It should have been
myObject = new MyObject(0);
Now that this is cleared, you have other defects in the code.
QMutex mutex; is local and will always be acquired by the same thread. which means he has no purpose. Instead it should be a private member of MyObject
MyWidget::pbStop_Slot should be a method of MyObject instead, otherwise you have a race condition when accessing Stop member. Remember the mutex above? It is the time to use it. By the way, with your implementation call directly the method, because the even loop of cThread is only executing doWork
MyObject::pbStop_Slot()
{
mutex.lock()
Stop = true;
mutex.unlock()
}
Now your program should be technically correct. But It suck that you cannot use signals and slots because your thread is blocked executing doWork. Also, can we do something about that lock. In fact, yes. The way I will go is to use a Qtimer as a heartbeat ech 100ms rather than asking the thread to sleep. But to not change much your code you can use QAbstractEventDispatcher * QAbstractEventDispatcher::instance ( QThread * thread = 0 ) directly
MyObject::pbStop_Slot() //becomes a real slot again
{
// no more mutex
Stop = true;
}
....
//this connection is changed
connect(pbStop, SIGNAL(clicked()), myObject, SLOT(pbStop_Slot()));
....
void MyObject::doWork() {
for (int i = 0; i < 100; i++) {
//no mutex
if (this->Stop) {
break;
}
// output into a text field
emit numberChanged(i);
// output on the console
std::cout << "running " << (QString::number(i)).toStdString() << std::endl;
//process events, to allow stop to be processed using signals and slots
QAbstractEventDispatcher::instance(cThread)->processEvents();
QThread::msleep(100);
}
}
A warning about processEvents. As it is now, if the user presses run while dowork is being executed it will be called within itself. You have a nasty code right now. An easy way to circumvent this is to put a boolean which is checked and set at the beginning of dowork.
dowork(){
if(isdoingwork)
return;
isdoingwork = true
for(...
This is a poor man way of achieving reentrancy . You will see the word reentrant quite often in Qt documentation.
Good luck in your multithreading trip.
Very good UmNyobe!
For reference I've added the corrected code here.
// MyWidget.h
#ifndef MYWIDGET_H_
#define MYWIDGET_H_
#include <QtWidgets/QWidget>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton>
#include "../MyObject.h"
#include <QThread>
class MyWidget : public QWidget {
Q_OBJECT
public:
MyWidget(QWidget* parent = 0);
~MyWidget();
QPushButton* pbRun;
QPushButton* pbStop;
QPushButton* pbExit;
QLineEdit* txtCount;
QThread* wThread;
MyObject* myObject;
public slots:
void onNumberChanged(int);
private slots:
void pbRun_Slot();
// void pbStop_Slot();
void pbExit_Slot();
};
#endif /* MYWIDGET_H_ */
-----------------------------------------
// MyWidget.cpp
#include "MyWidget.h"
#include "../K.h"
#include <iostream>
MyWidget::MyWidget(QWidget* parent) : QWidget(parent) {
pbRun = new QPushButton(this);
pbRun->setObjectName(QStringLiteral("pbRun"));
pbRun->setGeometry(QRect(20, 20, 80, 40));
pbRun->setText("Run");
connect(pbRun, SIGNAL(clicked()), this, SLOT(pbRun_Slot()));
pbStop = new QPushButton(this);
pbStop->setObjectName(QStringLiteral("pbStop"));
pbStop->setGeometry(QRect(20, 80, 80, 40));
pbStop->setText("Stop");
pbExit = new QPushButton(this);
pbExit->setObjectName(QStringLiteral("pbExit"));
pbExit->setGeometry(QRect(20, 140, 80, 40));
pbExit->setText("Exit");
connect(pbExit, SIGNAL(clicked()), this, SLOT(pbExit_Slot()));
txtCount = new QLineEdit(this);
txtCount->setGeometry(QRect(20, 200, 80, 40));
txtCount->setStyleSheet("QLineEdit{background: white;}");
myObject = new MyObject(0);
connect(myObject, SIGNAL(numberChanged(int)), this, SLOT(onNumberChanged(int)));
connect(pbStop, SIGNAL(clicked()), myObject, SLOT(pbStop_Slot()));
}
MyWidget::~MyWidget() {
delete myObject;
delete wThread;
}
void MyWidget::pbRun_Slot() {
// start QThread*, wThread in the MyWidget class
wThread = new QThread(this);
myObject->doSetup(wThread);
myObject->moveToThread(wThread);
wThread->start();
}
void MyWidget::pbExit_Slot() {
// a static pointer of the main window
(K::SfMainWin)->close();
}
void MyWidget::onNumberChanged(int j) {
txtCount->setText(QString::number(j));
}
---------------------------------------------------------
// MyObject.h
#ifndef MYOBJECT_H_
#define MYOBJECT_H_
#include <QObject>
#include <QThread>
#include <QMutex>
class MyObject : public QObject {
Q_OBJECT
public:
explicit MyObject(QObject* parent = 0);
~MyObject();
void doSetup(QThread*);
int hold;
bool Stop;
int inx;
int lastUsedInx;
signals:
void numberChanged(int);
public slots:
void doWork();
void pbStop_Slot();
private:
bool isdoingwork;
QThread* pThread;
QMutex mutex;
};
#endif /* MYOBJECT_H_ */
----------------------------------------------------
// MyObject.cpp SF022
#include "MyObject.h"
#include <iostream>
#include <QAbstractEventDispatcher>
MyObject::MyObject(QObject* parent) : QObject(parent) {
Stop = false;
isdoingwork = false;
inx = 0;
lastUsedInx = 0;
}
MyObject::~MyObject() {
}
void MyObject::doSetup(QThread* thread) {
pThread = thread;
Stop = false;
isdoingwork = false;
connect(pThread, SIGNAL(started()), this, SLOT(doWork()));
}
void MyObject::pbStop_Slot() {
mutex.lock();
Stop = true;
isdoingwork = false;
mutex.unlock();
}
void MyObject::doWork() {
if(isdoingwork) {
return;
}
isdoingwork = true;
for (inx = lastUsedInx + 1; inx < 100; inx++) {
if (this->Stop) {
break;
}
// output into a text box
emit numberChanged(inx);
lastUsedInx = inx;
// process events, to allow stop to be processed using signals and slots
(QAbstractEventDispatcher::instance(pThread))->processEvents(QEventLoop::AllEvents);
QThread::msleep(800);
}
isdoingwork = false;
}