So, I have this simplified code as an example for my problem:
#include <QDebug>
#include <functional>
void third()
{
qInfo() << "finished everything!";
}
// will use stuff downloaded by `first()`, which will be used by `third`
void second(const std::function<void()>& cb = [] {})
{
// this has to be called after `first()` has been called and finished
// finished, calling callback
cb();
}
// Will download some stuff, which will be used by `second()`
void first(const std::function<void()>& cb = [] {})
{
// finished, calling callback
cb();
}
int main(int argc, char* argv[])
{
first([=] {
second(third);
});
// or, even worse
first([=] {
second([=] {
third();
});
});
}
Is there any way, that I can prevent this callback hell from happening?
I thought about making another thread, where I would call those functions, block that thread and wait for them to finish.
I am not super sure on how I would do that though. So, first of all, is there a better way to write this (maybe even without creating another thread?) and secondly, if I had to make this happen on another thread, how would I do that?
Thanks for any help in advance!
Signals and slots quite naturally clean up this kind of code. For example:
class Downloader : public QObject {
Q_OBJECT
public:
void beginDownload() // this is your first
{
emit downloadFinished({});
}
signals:
void downloadFinished(const QByteArray& data);
};
class DataProcessorA: public QObject {
Q_OBJECT
public:
void processData(const QByteArray& data) // this is your second
{
emit processingFinished({});
}
signals:
void processingFinished(const QDateArray& processedData);
};
class DataProcessorB: public QObject {
Q_OBJECT
public:
void processData2(const QByteArray& data) // this is your third
{
emit processingFinished({});
}
signals:
void processingFinished(const QDateArray& processedData);
};
void myFunction()
{
auto* downloader = new Downloader(parent);
auto* processorA = new DataProcessorA(parent);
auto* processorB = new DataProcessorB(parent);
// make first call second...
QObject::connect(downloader, &Downloader::downloadFinished, processorA, &DataProcessorA::processData);
// make second call third...
QObject::connect(processorA , &DataProcessorA::processingFinished, processorB, &DataProcessorB::processData);
}
This can work even if the download is executed on a separate thread because Qt's signals/slots can work between threads. The data in the signal will be copied to the receiving thread. Ref: https://doc.qt.io/qt-6/qt.html#ConnectionType-enum
There is a bit of boilerplate with all the QObject stuff, but this should allow you to write each piece in isolation and then be able to flexibly connect the individual pieces using QObject::connect.
Related
First off, I have read up on QThreads and using the QEventLoop but I am not entirely sure my implementation is correct.
TL;DR see Problem Details below.
Most useful sources of information is Qt Wiki, KDAB Qthread presentation (useful for w/ & w/o event loop), SO posts here and here relating to this question.
My scenario is:
I have a potentially very long running function with multiple I/O disk calls. Thus I need a thread to not block the UI. For this, I made my own implementation of a Thread.
TL;DR QThreads
My understanding is a QThread is a separate event-loop object, and requires either a custom run() implementation or have an object moved to the newly created thread object wherein the moved object(s) lives (and runs). What I described is w/ the event loop implementation.
Problem
I maybe be missing something as my implementation of this, what I described above, does not function correctly. How do I know this, well Qt Docs and SO posts above mention that QThread::quit() or QThread::exit() are dependent on the QEventLoop, and if the QThread::exec() was not run (by calling the QThread::run() via QThread::start()), then the quit() or exit() functions will never run, which is my problem.
My implementation philisophy is somethings similar to Java's Thread & Lambda syntax e.g.
new Thread(() -> { // some code to run in a different thread}).start();
I used the following implementation
Thread Object container of sorts where lambdas can be used
QWorkerThread: public QObject
// This is the thread that runs object below
----QWaitThread : public QThread
// This is the object which lives inside the above thread
----ThreadWorker : public QObject, public QInterruptable
Simple example usage would be (thread and child object cleanup done inside QWorkerThread):
QWorkerThread *workerThread = new QWorkerThread;
workerThread->setRunnable([](){
// insert CPU intensive or long running task here
});
workerThread->start();
Problem Detail/Example
// somewhere in main UI thread
workerThread->stop(); // or workerThread->kill()
which calls QThread::quit() or QThread::quit(), then QThread::terminate() followed by QThread::wait() will not terminate the thread. The long running process defined in the lambda (inside setRunnable()) will run until it is complete.
I know this post is longer that what is conventional, but I would prefer everyone to get the full 'picture' of what I am trying to achieve, as I am unsure of where my problem actually lies.
Any help would be gratefully appreciated!
Code Implementation
I will be posting all code for a full idea of implementation, incase I miss something important.
QWaitThread.h is the implementation of a QThread
#ifndef QWAITTHREAD_H
#define QWAITTHREAD_H
#include <QObject>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
class QWaitThread : public QThread
{
Q_OBJECT
public:
explicit QWaitThread(QObject *parent = nullptr);
~QWaitThread();
virtual void pause();
virtual void resume();
signals:
void paused();
void resumed();
public slots:
void pausing();
void resuming();
private:
QWaitCondition *waitCondition;
QMutex mutex;
};
#endif // QWAITTHREAD_H
QWaitThread.cpp
#include "qwaitthread.h"
QWaitThread::QWaitThread(QObject *parent) : QThread(parent)
{
waitCondition = new QWaitCondition;
}
QWaitThread::~QWaitThread()
{
if(waitCondition != nullptr) {
delete waitCondition;
}
}
void QWaitThread::pause()
{
emit paused();
waitCondition->wait(&mutex);
}
void QWaitThread::resume()
{
waitCondition->wakeAll();
emit resumed();
}
void QWaitThread::pausing()
{
pause();
}
void QWaitThread::resuming()
{
resume();
}
QInterruptable.h interface defines some expected functionality
#ifndef QINTERRUPTABLE_H
#define QINTERRUPTABLE_H
class QInterruptable {
public:
virtual void pause() = 0;
virtual void resume() = 0;
virtual void interrupt() = 0;
virtual ~QInterruptable() = default;
};
#endif // QINTERRUPTABLE_H
ThreadWorker.h is the Object that lives (and runs) inside QWaitThread
#ifndef THREADWORKER_H
#define THREADWORKER_H
#include <QObject>
#include <functional>
#include <QWaitCondition>
#include <QMutex>
#include "QInterruptable.h"
class ThreadWorker : public QObject, public QInterruptable
{
Q_OBJECT
private:
QMutex mutex;
QWaitCondition *waitCondition;
std::function<void ()> runnable;
bool shouldPause = false;
public:
explicit ThreadWorker(QObject *parent = nullptr);
ThreadWorker(std::function<void ()> func);
~ThreadWorker();
void setRunnable(const std::function<void ()> &value);
signals:
/**
* Emitted when the QWorkerThread object has started work
* #brief started
*/
void started();
/**
* #brief progress reports on progress in method, user defined.
* #param value reported using int
*/
void progress(int value);
/**
* Emitted when the QWorkerThread object has finished its work, same signal is used from &QThread::finished
* #brief started
*/
void finished();
/**
* Emitted when the QWorkerThread has encountered an error, user defined.
* #brief started
*/
void error();
public slots:
virtual void run();
virtual void cleanup();
// QInterruptable interface
public:
void pause()
{
shouldPause = true;
}
void resume()
{
shouldPause = false;
}
QMutex& getMutex();
QWaitCondition *getWaitCondition() const;
void setWaitCondition(QWaitCondition *value);
bool getShouldPause() const;
// QInterruptable interface
public:
void interrupt()
{
}
};
#endif // THREADWORKER_H
ThreadWorker.cpp
#include "threadworker.h"
void ThreadWorker::setRunnable(const std::function<void ()> &value)
{
runnable = value;
}
QMutex& ThreadWorker::getMutex()
{
return mutex;
}
QWaitCondition *ThreadWorker::getWaitCondition() const
{
return waitCondition;
}
void ThreadWorker::setWaitCondition(QWaitCondition *value)
{
waitCondition = value;
}
bool ThreadWorker::getShouldPause() const
{
return shouldPause;
}
ThreadWorker::ThreadWorker(QObject *parent) : QObject(parent)
{
waitCondition = new QWaitCondition;
}
ThreadWorker::ThreadWorker(std::function<void ()> func): runnable(func) {
waitCondition = new QWaitCondition;
}
ThreadWorker::~ThreadWorker()
{
if(waitCondition != nullptr){
delete waitCondition;
}
}
void ThreadWorker::run()
{
emit started();
runnable();
emit finished();
}
void ThreadWorker::cleanup()
{
}
QWorkerThread.h the main class of interest, where the runnable lambda is accepted and where the main 'thread' processing happens, moving to thread, starting thread, handling events, etc
#ifndef QWORKERTHREAD_H
#define QWORKERTHREAD_H
#include <QObject>
#include <functional>
#include <QThread>
#include <QEventLoop>
#include "qwaitthread.h"
#include "threadworker.h"
class QWorkerThread: public QObject
{
Q_OBJECT
public:
enum State {
Running,
Paused,
NotRunning,
Finished,
Waiting,
Exiting
};
QWorkerThread();
explicit QWorkerThread(std::function<void ()> func);
~QWorkerThread();
static QString parseState(QWorkerThread::State state);
virtual void setRunnable(std::function <void()> runnable);
virtual void start(QThread::Priority priority = QThread::Priority::InheritPriority);
virtual void stop();
virtual void wait(unsigned long time = ULONG_MAX);
virtual void kill();
virtual void setWorkerObject(ThreadWorker *value);
virtual void pause();
virtual void resume();
virtual QWaitThread *getWorkerThread() const;
State getState() const;
signals:
/**
* Emitted when the QWorkerThread object has started work
* #brief started
*/
void started();
/**
* #brief progress reports on progress in method, user defined.
* #param value reported using int
*/
void progress(int value);
/**
* Emitted when the QWorkerThread object has finished its work, same signal is used from &QThread::finished
* #brief started
*/
void finished();
/**
* Emitted when the QWorkerThread has encountered an error, user defined.
* #brief started
*/
void error();
private:
/**
* #brief workerObject - Contains the object and 'method' that will be moved to `workerThread`
*/
ThreadWorker *workerObject = nullptr;
/**
* #brief workerThread - Worker Thread is seperate thread that runs the method
*/
QWaitThread *workerThread = nullptr;
State state = State::NotRunning;
};
#endif // QWORKERTHREAD_H
QWorkerThread.cpp implementation
#include "qworkerthread.h"
QWorkerThread::QWorkerThread()
{
state = State::NotRunning;
workerThread = new QWaitThread;
workerObject = new ThreadWorker;
workerThread->setObjectName("WorkerThread");
}
QWorkerThread::QWorkerThread(std::function<void ()> func)
{
state = State::NotRunning;
workerThread = new QWaitThread;
workerObject = new ThreadWorker(func);
workerThread->setObjectName("WorkerThread");
}
QWorkerThread::~QWorkerThread()
{
// Check if worker thread is running
if(workerThread->isRunning()) {
// Exit thread with -1
workerThread->exit(-1);
}
if(!workerThread->isFinished()) {
workerThread->wait(500);
if(workerThread->isRunning()) {
workerThread->terminate();
}
}
// cleanup
delete workerObject;
delete workerThread;
}
void QWorkerThread::setRunnable(std::function<void ()> runnable)
{
workerObject->setRunnable(runnable);
}
void QWorkerThread::start(QThread::Priority priority)
{
state = State::Running;
// Connect workerThread start signal to ThreadWorker object's run slot
connect(workerThread, &QThread::started, workerObject, &ThreadWorker::started);
connect(workerThread, &QThread::started, workerObject, &ThreadWorker::run);
// Connect threadWorker progress report to this progress report
connect(workerObject, &ThreadWorker::progress, this, &QWorkerThread::progress);
// Cleanup
connect(workerObject, &ThreadWorker::finished, this, [this](){
state = State::Finished;
emit finished();
});
connect(workerThread, &QWaitThread::finished, this, [this] {
workerObject->deleteLater();
});
// move workerObject to thread
workerObject->moveToThread(workerThread);
// emit signal that we are starting
emit started();
// Start WorkerThread which invokes object to start process method
workerThread->start(priority);
}
void QWorkerThread::stop()
{
state = State::Exiting;
// Exit thread safely with success
workerThread->quit();
emit finished();
}
void QWorkerThread::wait(unsigned long time)
{
state = State::Waiting;
workerThread->wait(time);
}
void QWorkerThread::kill()
{
// try stopping
stop();
// check if still running
if(workerThread->isRunning()){
// forcefully kill
workerThread->terminate();
workerThread->wait();
}
emit finished();
}
void QWorkerThread::setWorkerObject(ThreadWorker *value)
{
workerObject = value;
}
QWaitThread *QWorkerThread::getWorkerThread() const
{
return workerThread;
}
QWorkerThread::State QWorkerThread::getState() const
{
return state;
}
QString QWorkerThread::parseState(QWorkerThread::State state) {
switch (state) {
case Running:
return "Running";
case Paused:
return "Paused";
case NotRunning:
return "NotRunning";
case Finished:
return "Finished";
case Waiting:
return "Waiting";
case Exiting:
return "Exiting";
}
return QString("Unknown State [%1]").arg(QString::number(state)) ;
}
void QWorkerThread::pause()
{
workerObject->pause();
state = State::Paused;
}
void QWorkerThread::resume()
{
workerObject->resume();
state = State::Running;
}
Update with Some extra info
Regarding the ~QWorkerThread(), I noticed that when calling delete QThread or QThread::deleteLater(), the QWaitThread() (or QThread) will throw a Fatal error: Thread destroyed while it is still running. This is after quit()/terminate() was called.
The following line from QThread.cpp
if (d->running && !d->finished && !d->data->isAdopted)
qFatal("QThread: Destroyed while thread is still running");
where
d->running == true
d->finished == false
d->data->isAdopted ?
I've tested your code and here is what I realized.
As you mentioned, terminate() doesn't completely stop a thread.
Qt doc says:
Terminates the execution of the thread. The thread may or may not be terminated immediately, depending on the operating system's scheduling policies. Use QThread::wait() after terminate(), to be sure.
Unfortunately, the wait() freezes even after terminate(). It might be a problem with your code, but I created a maximally simplified example to verify this, which still has the same problems.
Firstly, here is the part of your code what I would suggest to change:
QWorkerThread::~QWorkerThread()
{
...
// cleanup
delete workerObject; // Unsafe, but the only way to call the destructor, if necessary
delete workerThread; // qFatal
}
Here is what Qt doc says about the destructor unsafety:
Calling delete on a QObject from a thread other than the one that owns the object (or accessing the object in other ways) is unsafe, unless you guarantee that the object isn't processing events at that moment. Use QObject::deleteLater() instead, and a DeferredDelete event will be posted, which the event loop of the object's thread will eventually pick up. By default, the thread that owns a QObject is the thread that creates the QObject, but not after QObject::moveToThread() has been called.
Note. Changing delete workerThread to workerThread->deleteLater() works for me without qFatal.
Ok, what problems we actually have:
Desctructor of QThread subclass cannot be called directly after terminate() due to qFatal
wait() freezes and cannot be used after terminate() despite to docs
(seems the problem is actual only when an infinite operation is moved into event loop)
Make sure the problem is not in another place of your code
Minimum reproducible example
Worker.h
#pragma once
#include <QObject>
class Worker : public QObject
{
Q_OBJECT
public:
~Worker();
public slots:
void process();
};
Worker.cpp
#include "Worker.h"
#include <QThread>
#include <QDebug>
#include <QDateTime>
Worker::~Worker()
{
qDebug() << "~Worker()";
}
void Worker::process()
{
qDebug("Hello World!");
while(true)
{
qDebug() << QDateTime::currentDateTime();
QThread::msleep(100);
}
}
MainWin.h
#pragma once
#include <QtWidgets/QMainWindow>
class QThread;
class Worker;
class MainWin : public QMainWindow
{
Q_OBJECT
public:
MainWin(QWidget *parent = nullptr);
~MainWin();
private:
QThread* thread = nullptr;
Worker* worker = nullptr;
};
MainWin.cpp
#include "MainWin.h"
#include "Worker.h"
#include <QThread>
#include <QDebug>
#include <QDateTime>
MainWin::MainWin(QWidget *parent)
: QMainWindow(parent)
{
thread = new QThread;
worker = new Worker;
worker->moveToThread(thread);
// Start only one infinite operation
connect(thread, &QThread::started, worker, &Worker::process);
thread->start();
}
MainWin::~MainWin()
{
if (thread->isRunning())
{
thread->exit(-1);
thread->wait(500);
}
if (thread->isRunning())
{
thread->terminate();
}
//cleanup
delete worker;
delete thread; // qFatal("QThread: Destroyed while thread is still running")
}
The only working code what I've found
MainWin::~MainWin()
{
...
//cleanup
delete worker; // Worker destructor will be called, but be note this is unsafe
thread->deleteLater(); // Allows to avoid qFatal but make thread terminated
}
Conclusions and suggestions
Everything I can offer besides to avoid terminate() at all, is to use terminate() without wait() and then workerThread->deleteLater().
If time-consuming operation which you're trying to terminate, is your own code, consider embedding some terminate flag into the code.
It would be better to avoid raw pointers and replace they by smart pointers, where possible.
What else I can offer as universal way to run lambdas in threads
Simplified example how can one use lamdas, signals-slots, threads, started-finished signals, QtConcurrent::run() and QFuture<>. This way you can achieve both running code in one persistent additional thread and inside an automatic thread pool as well. But termination is not supported.
LambdaThread.h
#pragma once
#include <QObject>
#include <functional>
#include <QFuture>
class QThreadPool;
class LambdaThread : public QObject
{
Q_OBJECT
public:
// maxThreadCount = -1 to use idealThreadCount by default
LambdaThread(QObject *parent, int maxThreadCount = -1);
signals:
void started();
void finished();
public slots:
// Invoke this directly or by a signal
QFuture<void> setRunnable(std::function<void()> func);
private:
/*
For the case you need persistent thread sometimes.
In the case you never need persistent thread,
just remove m_threadPool from this class at all
*/
QThreadPool* m_threadPool = nullptr;
};
LambdaThread.cpp
#include "LambdaThread.h"
#include <QtConcurrent/QtConcurrent>
#include <QThreadPool>
LambdaThread::LambdaThread(QObject *parent, int maxThreadCount /*= -1*/)
: QObject(parent)
{
m_threadPool = new QThreadPool(this);
if(maxThreadCount > 0)
{
m_threadPool->setMaxThreadCount(maxThreadCount);
if (maxThreadCount == 1)
{
// Avoid thread affinity changing
m_threadPool->setExpiryTimeout(-1);
}
}
}
QFuture<void> LambdaThread::setRunnable(std::function<void()> func)
{
return QtConcurrent::run(m_threadPool,
[this, func]()
{
// Be note that you actually need event loop in a receiver thread only
emit started();
func();
emit finished();
});
}
Just GUI class example where you can start your runnables and receive signals.
MainWin.h
#pragma once
#include <QtWidgets/QMainWindow>
#include <functional>
class LambdaThread;
class MainWin : public QMainWindow
{
Q_OBJECT
public:
MainWin(QWidget *parent = nullptr);
signals:
// For the case you want to use signals
void newRunnable(std::function<void()> func);
private:
LambdaThread* m_lambdaThread = nullptr;
};
MainWin.cpp
#include "MainWin.h"
#include "LambdaThread.h"
#include <QFuture>
#include <QDebug>
MainWin::MainWin(QWidget *parent)
: QMainWindow(parent)
{
m_lambdaThread = new LambdaThread(this);
connect(this, &MainWin::newRunnable,
m_lambdaThread, &LambdaThread::setRunnable);
/*
Do not forget the third (`this`) context variable
while using modern signal-slot connection syntax with lambdas
*/
connect(m_lambdaThread, &LambdaThread::started,
this, []()
{
qDebug() << "Runnable stated";
});
connect(m_lambdaThread, &LambdaThread::finished,
this, []()
{
qDebug() << "Runnable finished";
});
// Set your lambda directly
QFuture<void> future = m_lambdaThread->setRunnable([]()
{
qDebug() << "hello from threaded runnable";
});
// You can also use future (not necessary of course)
//future.waitForFinished();
// Or you can emit your lambda via the signal:
emit newRunnable([]()
{
qDebug() << "hello from threaded runnable which comes from signal";
});
}
I need to send a signal from static callback function. I do the following:
.h
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow (QWidget* parent=0);
~MainWindow();
static MainWindow* getInstance();
private:
void emitSignal();
static MainWindow* m_instance;
signals:
void mysignal();
slots:
void print();
...
}
.cpp
MainWindow* MainWindow::m_instance = 0;
MainWindow::MainWindow(QWidget* parent):
QMainWindow(parent) {
...
}
MainWindow* MainWindow::getInstance() {
if (m_instance == 0)
m_instance = new MainWindow;
return m_instance;
}
void MainWindow::emitSignal() {
emit mysignal();
}
inside of callback function:
getInstance()->emitSignal();
and somewhere in code:
connect(MainWindow::getInstance(), SIGNAL(mysignal()), this, SLOT(print()));
But the slot print() is not executed. And no errors are printed. Where am I wrong?
The callback uses getInstance() and thus can definitely create a new widget if one doesn't exist yet. Then it's a matter of how well you understand the code that executes the callback. If the callback is made before you make the connection, then the slot won't be invoked. That's likely your problem. It is invalid to invoke the callback without an instance already in place, and you should assert that it is so.
Most likely, you do not need to create an instance of the main window on the fly. You're likely facing an XY problem where you think you need to do something, but you're so invested in a solution that doesn't work that you don't see the bigger problem you're trying to solve. Please clearly expose what exactly is your application - why are you creating the window on the fly?
It is also unnecessary to create a method that forwards to the signal, unless you really somehow need the signal to be private. I doubt that you do - don't make it private.
The code below demonstrates that what you're trying to do definitely works if you do it correctly:
// https://github.com/KubaO/stackoverflown/tree/master/questions/static-signal-48540601
#include <QtCore>
class Main : public QObject {
Q_OBJECT
static Main * m_instance;
public:
Main(QObject * parent = {}) : QObject(parent) {
Q_ASSERT(! hasInstance());
m_instance = this;
}
static Main * instance() {
if (! m_instance) m_instance = new Main;
return m_instance;
}
static bool hasInstance() { return m_instance; }
Q_SIGNAL void theSignal();
};
Main * Main::m_instance;
void callback() {
Q_ASSERT(Main::hasInstance());
// If the instance didn't exist here, nothing can receive the signal.
Main::instance()->theSignal();
}
int main()
{
int slotCalls = {};
Main object;
QObject::connect(&object, &Main::theSignal, [&]{ slotCalls ++; });
Q_ASSERT(slotCalls == 0);
callback();
Q_ASSERT(slotCalls == 1);
}
#include "main.moc"
Are you sure the signal connected to the slot?
To avoid typos you could use this syntax (as long as you dont overload signals/slots you are fine with that ):
connect(MainWindow::getInstance(), &MainWindow::mysignal, this, &MainWindow::print);
another hint/styleguide: name your signals like signal_mySignal and slots like slot_print to avoid typos and clear things up.
You can also call this->dumpObjectInfo(); from somewhere in your main (after the connects of course) and take a look at the output
Essentially, I'm trying to add a Qt GUI onto an already existing GLUT application I made, and I'm not sure how to pass signals from the existing code into Qt, without introducing the overhead of making dozens of existing classes QObjects. Currently I'm using a QTimer and an event loop, but that isn't scaling very well, seeming to involve a lot of waiting for mutexes to then decide there's no work, with attempts to resolve this more increasing coupling than fixing it.
The best alternate I could come up with is this:
static std::mutex relay_mutex;
static std::condition_variable relay_condition;
static std::queue<std::pair<int, void*> > relay_queue;
extern "C"
void passMessage(int a, void * b)
{
std::lock_guard<std::mutex> lock(relay_mutex);
relay_queue.emplace_back(a, b);
relay_condition.notify_all();
}
class RelayThread : public QThread
{
Q_OBJECT
public:
RelayThread() {}
signals:
void passMessage(int, void *);
protected:
void run() override
{
std::unique_lock<std::mutex> lock(relay_mutex);
for(;;)
{
while(relay_queue.empty())
relay_condition.wait();
do {
emit passMessage(relay_queue.front().first, relay_queue.front().second);
if(relay_queue.front().first == 0)
return;
relay_queue.pop()
} while(relay_queue.size());
}
}
};
But this will obviously incur the overhead of an extra context switch, which is no good either. Is there a way to manually queue up a signal without going through a QThread like this?
This should be sufficient. You can let Qt do the queueing, if needed, by setting the connection type to Qt::QueuedConnection, the value of the third argument of the connect() call.
#include <QObject>
void passMessage(int a, MessageType b) {
Relay r(a,b);
}
class Relay: public QObject {
Q_OBJECT
public:
Relay(int a, MessageType b) {
emit passMessage(a, b);
}
signals:
void passMessage(int, MessageType);
};
You will also need to register a metatype for MessageType. See here.
I have a button with a slot connected to its clicked() signal.
The slot is a time consuming task and take many seconds.
When trying to re-click the button before the slot ends, I can't.
How to make the button responsive when running the slot connected to its clicked() signal?
Anything running in the GUI must not block nor take much time to finish. Don't do long-running things in the GUI thread. That will fix your problem.
Now you may ask: so how do I do long-running things, then? You do them in other threads, but that doesn't imply having to ever see an instance of a QThread, though.
You have to isolate the data and the operations on it. Suppose we have a following class:
class MyData {
public:
MyData() {}
/// Loads and pre-processes the data
bool load(const QString & file) { QThread::sleep(5); return true; }
/// Transforms the data
void transform() { QThread::sleep(2); }
};
using MyDataPtr = std::shared_ptr<MyData>;
Q_DECLARE_METATYPE(MyDataPtr)
int main(...) { qRegisterMetaType<MyDataPtr>(); ... }
To make it most general, we assume that MyData is expensive to copy and move.
If the operation is of a "generate new data" kind, say like reading a file, processing the data and building up a data structure, you can instantiate the data in a worker thread and pass it to the GUI thread:
class GuiClass : public QWidget {
Q_OBJECT
MyDataPtr data;
Q_SIGNAL void hasData(const MyDataPtr &);
public:
GuiClass() {
connect(this, &GuiClass::hasData,
this, [this](const MyDataPtr & newData){ data = newData; });
}
Q_SLOT void makeData(const QString & file) {
QtConcurrent::run([=]{
auto data = std::make_shared<MyData>();
if (data->load(file))
emit hasData(data);
});
}
If you need to transform the data already in place, some common approaches are:
Copy the data, transform it in a worker thread, then swap it out after the transformation.
Q_SLOT void transformData1() {
MyDataPtr copy = std::make_shared<MyData>(*data);
QtConcurrent::run([=]{
copy->transform();
emit hasData(copy);
});
}
Temporarily remove the data, transform it, then put it back. The UI will have to cope with missing data, e.g. by showing a "busy" indicator when data is null.
Q_SLOT void transformData2() {
MyDataPtr data = this->data;
this->data.reset();
QtConcurrent::run([=]{
data->transform();
emit hasData(data);
});
}
If MyData were easily movable, and the default value had a meaning of "null"/"empty", you could hold on to it by value instead:
class GuiClassVal : public QWidget {
Q_OBJECT
MyData data;
struct DataEvent : public QEvent {
static const QEvent::Type type;
MyData data;
DataEvent(const MyData & data) : QEvent{type}, data{data} {}
DataEvent(MyData && data) : QEvent{type}, data{std::move(data)} {}
};
/// This method is thread-safe.
void setData(MyData && data) {
QCoreApplication::postEvent(this, new DataEvent{std::move(data)});
}
/// This method is thread-safe.
void setData(const MyData & data) {
QCoreApplication::postEvent(this, new DataEvent{data});
}
bool event(QEvent *event) override {
if (event->type() == DataEvent::type) {
data.~MyData();
new (&data) MyData{std::move(static_cast<DataEvent*>(event)->data)};
return true;
}
return QWidget::event(event);
}
void transformInEvent(DataEvent * ev){
ev->data.transform();
QCoreApplication::postEvent(this, ev);
};
public:
Q_SLOT void makeData(const QString & file) {
QtConcurrent::run([=]{
MyData data;
if (data.load(file)) setData(std::move(data));
});
}
Q_SLOT void transformData1() {
QtConcurrent::run(this, &GuiClassVal::transformInEvent, new DataEvent{data});
}
Q_SLOT void transformData2() {
QtConcurrent::run(this, &GuiClassVal::transformInEvent, new DataEvent{std::move(data)});
}
};
const QEvent::Type GuiClassVal::DataEvent::type = (QEvent::Type)QEvent::registerEventType();
The event-based magic sauce can of course be factored out.
You have two high-level choices:
spawn the time-consuming choices into a thread: the simplest way is to use QtConcurrent::run(), and on the GUI side you'll need to worry about handling all the GUI events appropriately while your operation executes.
within your time-consuming code, call QApplication::processEvents() often enough that the GUI remains responsive (e.g., on the inside of a loop): this won't work if the time-consuming operation is 3rd party code you can't break out of, and you'll still need to work out how to handle GUI operations while you've got your long-running task executing.
You'll find that the first choice is by far the consensus better choice over the second, FWIW.
I'm learning Qt and I was reading about Threads, Events and QObjects from Qt wiki, and followed the wiki recommendations on how to handle some work in a while condition but its not working for my specific case. Here's a simple example of what I'm currently trying to achieve.
class FooEvents : public FooWrapper {
public virtual serverTime(..) { std::cout << "Server time event\n"; }
public virtual connected(..) { std::cout << "Connected event\n"; }
}
class Foo : public QObject {
private:
FooAPI *client;
public:
Foo(FooEvents *ev, QObject *parent = 0) : client(new FooApi(ev)) { .. }
private slots:
void processMessages() {
if (state is IDLE)
reqFooAPiServerTime();
select(client->fd()+1, ...);
if (socket is ready for read)
client.onReceive();
}
public:
void connect(...) {
if (connection) {
QObject::connect(&timer, SIGNAL(timeout()), this, SLOT(processMessages()));
timer.start(1000); // I don't get the output from FooEvents
}
}
}
This is a very simple but I think it illustrates my case. Why is this not working and what other alternatives to I have to handle this case? Thanks.s
Edit: The processMessages is being called every second but I don't get any output from the events
Where is timer declared and defined?
If it's local to Foo::connect() it'll be destroyed before it ever has a chance to fire. Presumably it just needs to be a member object of the Foo class.
Also keep in mind that QObject provides it's own simple interface to a timer - just override the protected virtual timerEvent() function and call QObject's startTimer() to start getting those timer events. In this case instead of having a slot to receive the timer events, they will just end up at the overridden timerEvent() function:
protected:
void timerEvent(QTimerEvent *event) {
processMessages();
}
public:
void connect( /* ... */ ) {
// ...
startTimer(1000);
}
This won't work, because processMessages() is not a SLOT.
So Declare processMessages() as a private slot and then try.
You don't declare the timer neither the slot. In the header you must declare:
class ... {
QTimer timer;
...
private slots:
void processMessages();
...
};
Then remember to make the SIGNAL-SLOT connection and configure the timer:
connect(&timer, SIGNAL(timeout()), this, SLOT(processMessages()));
timer.setInterval(1000);
timer.start();
Also timer.start(1000); would be valid...
ANOTHER POSSIBILITY
Other possibility would be to use the timer associated with each Q_OBJECT and overload the timerEvent:
class ... {
Q_OBJECT
...
protected:
void timerEvent(QTimerEvent *event);
...
};
Then you must implement the timer event as this:
void MyClass::timerEvent(QTimerEvent *event) {
processMessages();
}
And you can configure the timer with a simple call to startTimer(1000);