[Solved] Can't stop my own thread from the main thread
-
Hello. I'm a beginner at studying threads and I need some help. A have looked through a lot of posts, but coudn't find the necessary answer. So, I would be very greatful if anyone could give me some advice.
I'm creating an additional thread for updating my database(it takes a long time and I have to provide a user with an opportunity to work with my application while updating ). A have this class (.cpp)@#include "loadingworker.h"
LoadingWorker::LoadingWorker(QObject *parent) :
QObject(parent)
{}
void LoadingWorker::setFile(QString FileName)
{
file=new QFile(FileName,this);
}
void LoadingWorker::Inintialize()
{
thread=new QThread();connect(thread, SIGNAL(started()), this, SLOT(LoadingProcess()));
connect(this, SIGNAL(finished()), thread, SLOT(quit()));
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
this->moveToThread(thread);
thread->start();}
void LoadingWorker::LoadingProcess()
{
//updating my database
emit finished();
}@In my mainwindow.cpp I have this code
@QProgressDialog *ProgressDialog=new QProgressDialog(QObject::trUtf8("Updating"),QObject::trUtf8("Stop"),0,100, this);
worker=new LoadingWorker();
connect(worker,SIGNAL(ValueChanged(int)),ProgressDialog, SLOT(setValue(int)));
connect(worker,SIGNAL(finished()),SLOT(MessageSlot()));
connect(ProgressDialog,SIGNAL(canceled()),this, SLOT(AskUserUpdating()));
ProgressDialog->show();
worker->setFile(updateforuserDialog.getFileName());
worker->Inintialize();@So when a user clicks CANCEL on the ProgressDialog, the updating should stop and I have to quit my own thread and delete it and the object of a LoadingWorker class. And I don't know how to do it in the right way. I mean how to interrupt the runnig thread from the main thread. I did it in this way. I connect canceled() signal and ask the user if he really wants to stop updating. I he says YES, I emit the signal finished()
@worker->finished();@I clearly understand that the slot quit() for the thread in this line
@connect(this, SIGNAL(finished()), thread, SLOT(quit()));@won't work cause I emit a signal from the main thread and the object
"worker" belongs to my own thread. I tried to use Qt::BlockingQueuedConnection, but as I expected it caused a dead lock. In my logic I have to use Qt::DirectConnection here, but it doesn't work too and moreover I read that it's unsafe.
Waiting for any help. Thanx a lot in advance! -
Hi,
First of all, there is a problem in your code. @this->moveToThread(thread);@
should NEVER BE USED. It's a problem created in Qt3 documentation and carried into modern Qt releases. When reading the Qt5.1 documentation it's not recommended to subclass QThread and use the this->moveToThread option. Read this post:
"Threads done right":http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
It's an older so post, so don't read the section that the Qt documentation doesn't include this option. It does.
When using the 'new' way, use the Thread->exit() function to stop the function from outside the worker thread.
Hope this helps. -
Thank you so much for your answer! I tried to change my code and used the link you gave to me. But still it doesn't work the way I want it. And I'm very sorry, but I didn't understand about Thread->exit(). In the link you gave above nothing was told about this function and its use. I read the documentation several times, but didn't undestand the difference between quit() and exit(), except quit() is a slot and exit() is a function. So...now my code in the mainwindow.cpp looks like that:
@QProgressDialog *ProgressDialog=new QProgressDialog(QObject::trUtf8("Updating"),QObject::trUtf8("Stop"),0,100, this);
QThread *thread=new QThread();
worker=new LoadingWorker();
worker->setFile(updateforuserDialog.getFileName());
worker->moveToThread(thread);
connect(thread, SIGNAL(started()), worker, SLOT(LoadingProcess()));connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), thread, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
connect(worker,SIGNAL(ValueChanged(int)),ProgressDialog, SLOT(setValue(int)));
connect(worker,SIGNAL(finished()),SLOT(MessageSlot()));
connect(ProgressDialog,SIGNAL(canceled()),this, SLOT(AskUserUpdating()));
ProgressDialog->show();
thread->start();@ -
First of please change this line (line 11 in the code snippet above):
@
connect(worker, SIGNAL(finished()), thread, SLOT(deleteLater()));
@
to this
@
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
@Secondly: To be able to cancel the work executed in the second thread you need to implement a slot for your worker object which will stop the processing in your LoadingProcess() slot when triggered. Then you can connect a signal (e.g. clicked() signal from PushButton) to this slot.
But be aware that the slot stopLoadingProcess() or whatever you may call it is only execute when you return back to the eventloop in between your LoadingProcess() slot. -
By saying " slot stopLoadingProcess() or whatever you may call it is only execute when you return back to the eventloop " you mean I won't be able to stop the LoadingProcess while it is being executed by my own thread?Very sorry to ask the same questions, but it seems very complicated to me:=) I undestand that the signal is delayed cause the object WORKER belongs to the different thread. So, how to make this signal connect with the slot stopLoadingProcess() at the same moment I press cancel?...What should I do? I mean what is the point of creating stopLoadingProcess() slot if it doesn't work at the time I need it. Thank you very much for your help!=)
-
Exactly. Thats why you need to return to the eventloop in between the execution of the LoadingProcess slot.
Example:
@
void LoadingWorker::stopLoadingProcess()
{
m_bCancelExecution = true;
}// this never returns to the eventloop during execution
void LoadingWorker::LoadingProcess()
{
//updating my database
for(int i = 0; i < someSize; ++i)
{
if(!m_bCancelExecution)
{
processEntry(i);
}
}
emit finished();
}// this returns in between each step
void LoadingWorker::LoadingProcess()
{
if(!m_bCancelExecution)
{
//updating my database
if(m_iCounter < someSize)
{
ProcessEntry(m_iCounter);
m_iCounter++;
QTimer::singleShot(100, this, SLOT(LoadingProcess()));
}
else
{
m_iCounter = 0;
emit finished();
}
}
}
@ -
I was just giving an example of a function in a worker object which does not return back to the eventloop during its work and a function which does return.
If you want to be able to stop your worker object while its doing its heavy processing stuff you need to return to the eventloop in between the processing(see second example). Otherwise the slot stopWork() or whatever will only be called after all the work is done (which is not what you want)
-
Yes, I understood that.Thank you)But even with your example I can't understand how I can get to the eventloop in the middle of the process. My slot is quite big and I don't see how exactly I can use your example with my own slot. A schematic code of my slot is
@if (file->open(QIODevice::ReadOnly | QIODevice::Text))
{
some code..
while(i<= k)
{
some code..
i++;
}
some code..
while(i<= n)
{
some code..
i++;
}
some code...
while(i<= m)
{
some code..
i++;
}
file->close();
emit finished();
}@Very sorry.Thank you so much!
-
Example:
@
void LoadingWorker::LoadingProcess()
{
if (file->open(QIODevice::ReadOnly | QIODevice::Text))
{
newFunction1();
}
}void LoadingWorker::newFunction1()
{
if(!m_bCancelExecution)
{
some code..
while(m_iCounter<= k)
{
some code..
m_iCounter++;
}
QTimer::singleShot(10, this, SLOT(function2()));
}
else
{
file->close();
emit finished();
}
}void LoadingWorker::newFunction2()
{
if(!m_bCancelExecution)
{
some code..
while(m_iCounter<= n)
{
some code..
m_iCounter++;
}
QTimer::singleShot(10, this, SLOT(function3()));
}
else
{
file->close();
emit finished();
}
}void LoadingWorker::newFunction3()
{
if(!m_bCancelExecution)
{
some code...
while(m_iCounter<= m)
{
some code..
m_iCounter++;
}
file->close();
emit finished();
}
else
{
file->close();
emit finished();
}
}
@Again this is just an example. You could divide this even further if you want to check for the boolean flag in the while loops by using something like the recursive approach I described in an example previously.
Example:
@
void LoadingWorker::newFunction1()
{
if(!m_bCancelExecution)
{
if(m_bFirstTimeInFunction1)
{
some code..
m_bFirstTimeInFunction1 = false;
}if(m_iCounter<= k) { some code.. m_iCounter++; QTimer::singleShot(10, this, SLOT(function1())); } else { QTimer::singleShot(10, this, SLOT(function2())); }
}
else
{
file->close();
emit finished();
}
}
@
You can also emit a signal instead of using QTimer::singleShot() but make sure to use QueuedConnection in the connect statement. -
Just thought of another solution to your problem. You can use "QCoreApplication::processEvents()":http://qt-project.org/doc/qt-5.0/qtcore/qcoreapplication.html#processEvents to force your thread to return to the event loop. Just make sure you don't call it too often, otherwise it might slow down your processing.
processEvents has some pitfalls so make sure you avoid them. For more information about it just "search for processEvents on this site":http://qt-project.org/search?search=processEvents.
Example:
@
if (file->open(QIODevice::ReadOnly | QIODevice::Text))
{
some code..
QApplication::processEvents();
while(i<= k)
{
some code..
i++;
QApplication::processEvents();
}
some code..
QApplication::processEvents();
while(i<= n)
{
some code..
i++;
QApplication::processEvents();
}
some code...
QApplication::processEvents();
while(i<= m)
{
some code..
i++;
QApplication::processEvents();
}
file->close();
emit finished();
}@