I have a QString object which is exported to qml. In C++ code while updating the value and emitting the changed signal for the property it does not update it because thread is busy: in that time I use a cost-operation in for loop. For that purpose I use QCoreApplication::processEvents() to be able to emit delayed signals on each iteration of the loop like:
foreach(const QVariant& item, _manifestFile) {
setStatusString(QString("Checking file %1 of %2...").arg(currentProcessingFile++).arg(totalFilesCount));
QCoreApplication::processEvents(); // TODO remove
//...
}
Where setStatusString is setter of my QString variable I described above:
void Updater::setStatusString(const QString &statusString) {
_statusString = statusString;
emit statusStringChanged();
}
How can I remove that processEvents() and be able to emit signals? Any solution is appreciated: threaded, Qt-meta object things, etc.
You should create your object of the class Updater on the heap and move it to a new thread in order to prevent the for loop from blocking main thread and the UI. This can be done like:
updater = new Updater();
QThread * th = new QThread();
updater->moveToThread(th);
QObject::connect(th,SIGNAL(started()),updater,SLOT(OnStarted()));
QObject::connect(th,SIGNAL(finished()),updater,SLOT(OnFinished()));
QObject::connect(updater,SIGNAL(statusStringChanged(QString)),this,SLOT(updateString(QString)));
th->start();
Your initialization and termination tasks in the class Updater should be done in OnStarted() and OnFinished() slots respectively.
Now you can emit the signal with the appropriate value which would be queued and processed in the appropriate time. You can emit the signal in a timer periodically in certain intervals to prevent from emitting too frequent.
And the last point is that you should not call Updater functions directly when it is in an other thread. The correct way is defining the functions as slots and connecting a signal to that slot and emitting the signal when you want to call a specific function.
Related
I have qml and c++ side of a project and I am exposing my C++ attributes via Q_INVOKABLE and Q_PROPERTY macros. My problem is even when I emit a signal in C++, qml side affected after some other code is executed. Can someone propose me a solution?
I have a status_ member variable and exposing it like this in my header;
Q_PROPERTY(QString statusProp MEMBER status_ NOTIFY status_changed)
Then, in the .cpp file, I have the function load_project;
QString controller::load_project(QString proj_name)
{
status_ = "Loading...";
emit status_changed();
clear_layers();
QString result = db_manager_.load_project(proj_name, engine_list_);
status_ = "";
emit status_changed();
return result;
}
Above, status is changed to "Loading...", however emit signal is received after db_manager.load_project() function, hence "Loading..." effect becomes invisible.
Could it be that the instance of your controller class used in the QML part of your application lives in the GUI thread? In this case, what happens is the following:
In your code, you set the status_ and emit the status_changed signal. This in fact delivers the signal to QML, but...
Control does not return to the event loop, but instead you are calling right into your db_manager_.load_project() method which - I guess - is blocking. After this method returns...
You set the status_ back to an empty string and once again emit status_changed.
As soon as you return from the controller::load_project method, control goes back to the Qt event loop and only now the QML part is rendered.
To change this, use threads (via QThread) to run the actual loading in a thread different than the GUI/QML one.
I'm creating a thread like this:
main.cpp
QThread acceptorThread;
acceptorObject acceptorobject;
acceptorobject.setupConnections(acceptorThread, simulation);
acceptorobject.moveToThread(&acceptorThread);
acceptorObject.cpp
void acceptorObject::setupConnections(QThread& thread, Simulation * simulation)
{
QObject::connect(&thread, SIGNAL(started()), this, SLOT(acceptNewClients()));
}
acceptNewClients() method works in infinite loop. At this point if I close my program I would get an error:
QThread destroyed while thread is still running
I looked through similar problems at stack and one guy said that I need to break the loop before finishing the thread in order to get rid of this bug. He also suggested to use a flag in infinite loop and emit a signal in destructor that will change the flag and eventually break the loop. It KINDA worked when I did something like this:
QObject::connect(&thread, SIGNAL(started()), this, SLOT(acceptNewClients()));
QObject::connect(this, SIGNAL(finishThread(bool)), this, SLOT(acceptNewClients(bool)));
And then emited finishThread(true) signal from destructor so I directly changed the flag. Of course I changed slot signature as well so it won't run in new thread anymore.
destructor code:
emit finishThread(true);
this->thread()->quit();
if(!this->thread()->wait(3000))
{
this->thread()->terminate();
this->thread()->wait();
}
How can I make this work?
What I've tried so far:
Adding a new slot that will change the flag. Result: when I close program the window dissapears but the proccess is still running. I think that destructor destroys the object before its emited signal is proccessed .
Making bool argument in acceptNewClients() slot a default one. Result: it overloads the funtion so one is run in different thread and the second one tries to change the flag which obviously doesn't work because they are completely different functions.
Solution:
connect(this, SIGNAL(finishThread()), &thread, SLOT(quit()));
connect(this, SIGNAL(finishThread()), this, SLOT(deleteLater()));
It was pointless to change slot function signature in this case.
In deconstructor I simply emit finishThread() signal, nothing more.
One way is instead of doing a while(true) in acceptNewClients you instead do
void acceptorObject::acceptNewClients(){
// do accept new client
QMetaObject::invokeMethod(this, "acceptNewClients", Qt::QueuedConnection);
}
In essence making the loop external.
The other option is to make everything use signals, QTcpServer (which I think you are using) has a newConnection signal you can connect to. Instead of using waitForNewConnection.
How to add a method within the class to a thread to execute?
I do not want to put "Pup" into a seperate class that inherits QThread as this is just an abstraction of some Legacy code I am working on.
void Dog::Pup()
{
printf("pup");
}
void Dog::Init()
{
QThread *dogThread = new QThread();
Pup->moveToThread(dogThread); //this is all wrong
Pup->connect(dogThread, ?, Pup, SLOT(Pup), ?)
dogThread.start();
}
Try this:
void Dog::Init()
{
QThread *dogThread = new QThread;
connect(dogThread, SIGNAL(started()), this, SLOT(Pup()), Qt::DirectConnection);
dogThread->start();
}
It basically creates a new QThread named dogThread and connects it's started() signal to the method you want to run inside the thread (Dog::Pup() which must be a slot).
When you use a Qt::QueuedConnection the slot would be executed in the receiver's thread, but when you use Qt::DirectConnection the slot will be invoked immediately, and because started() is emitted from the dogThread, the slot will also be called from the dogThread. You find more information about the connection types here: Qt::ConnectionType.
if you want to run a single function in another thread, you should check out the methods in the QtConcurrent namespace.
Read the Detailed description in the page http://doc.qt.io/qt-5/qthread.html
I am new to Qt and trying to learn the Qt threading mechanism. I am in a situation where I would like a background thread to perform some long running task and report the results to another (or main) thread after processing every 100 items. Right now I am doing this by emitting a signal from the background thread containing a list of the processed objects that is received in a slot in the main thread. Does Qt make a copy of the signal argument when it is received in the slot ? If so, how does how does calling qRegisterMetaType help with that ? This is what I am tying to accomplish in my code :
//background thread
void run(){
//get a query object from database
int fireCount = 0;
QList< QList<QVariant> > data;
while(query->next()){
fireCount++;
QList<QVariant> row;
//do some calculations on the fields read from the query
processRow(query,&row);
data.append(row);
if(fireCount>100){
emit publishDataToMainThread(data);
fireCount = 0;
data.clear();
}
}
}
//slot in main thread
void receiveData(QList< QList<Qvariant> > data){
\\display the data
}
Also , is this a recommended practice for transferring objects between threads ?
This is a perfectly fine way of doing it. QList uses implicit sharing (i.e. copy on write) so copying it means copying one pointer and increasing the reference count. It only gets copied once you try to modify it.
Just remember to use Qt::QueuedConnection when connection the signal to the slot so that the slots gets run in the receivers thread.
qRegisterMetaType or Q_DECLARE_METATYPE are needed so that you can pass parameters by value in signals. It tells the Qt Metatype system (which is sort of like reflection) that this type exists.
I have an app with such structure: all the datatypes (class INode) are stored in plugins (DLLs). Some of the datatypes can be drawn (if they're IDrawable).
To load an object of, e.g. class PointCloudNode: public INode I have a special input plugin (DLL) which is called class PointCloudParser: public IIOPlugin and IIOPlugin is a thread with some specific functionality: class IIOPlugin: public QThread.
All the objects are created by NodeFactory class which is a singleton stored in separate DLL.
And here's the problem:
void PointCloudNode::update()
{
QObject::connect (this,SIGNAL(tmptmp()),this,SLOT(drawObject()));
emit tmptmp();
}
If I do this from any thread (main thread or the Input Plugin thread)
NodeFactory* fab = NodeFactory::getInstance();
boost::shared_ptr<INode> pc(fab->createNode("pointCloud","myPC"));
boost::shared_ptr<IDrawable> dr = boost::dynamic_pointer_cast<IDrawable>(pc);
dr->update();
The update launches, the tmptmp() signal is emitted, and the slot (drawObject()) executes correctly.
BUT
if do just the same, but create the object in my Input Plugin, pass over the shared pointer and execute dr->update() in another function, the slot drawObject() is never entered though all the code is executed (including connect, etc.).
To be more precise, here's the Input Plugin:
void PointCloudParserPlugin::doLoad(const QString& inputName, boost::shared_ptr<INode> container)
{
NodeFactory* factory = NodeFactory::getInstance();
boost::shared_ptr<INode> node = factory->createNode("pointCloud", inputName);
// here goes the loading itself, nothing special...
container->addChild(node); //that's the container where I keep all the objects
//boost::dynamic_pointer_cast<IDrawable>(container->getChild(inputName))->update();
//If I uncomment this line, it all works: the slot is launched.
emit loadingFinished(inputName); // it executes the following function
}
The last emit is connected to this:
void GeomBox::updateVisualization(const QString& fileName)
{
boost::shared_ptr<INode> node = container_->getChild(fileName);
boost::shared_ptr<IDrawable> nodeDrawable = boost::dynamic_pointer_cast<IDrawable>(node);
nodeDrawable->update(); //this is the problem line: update() executes, connect() works, but the slot never runs :(
}
How come? The node object is the same all the way through, it is valid. Every line in code in launched, QObject::connect doesn't write anything to debug window, the signal tmptmp() is emitted, but the slot drawObject() in one case is never reached? Any ideas?
Upd.: If I do not inherit IIOPlugin from QThread, everything works fine (i.e. load the object in the main thread). I expected the signals/slots to work across the threads...
Since you are sending a signal across to a different thread, you might need to explicitly tell Qt that the connection should be a queued one:
QObject::connect(this, SIGNAL(tmptmp()), this, SLOT(drawObject()), Qt::QueuedConnection );
By default Qt will use Qt::AutoConnection as that last parameter, and it will choose whether to use a direct connection (if the slot is in the same thread as the emitter) or a queued connection (if the slot is in a different thread). But since your thread is in a separate library, maybe Qt isn't making the right assumption here.