Best QThread practices
-
Folks,
I have a thread I want to implement as a QThread. It is a fairly common process thread that would be (in pthreads)
void ThreadFunction() { // Open Log File while (!done) { // Process and write to log file usleep(100000); } /// Close Log File }
A) Set up start in thread (such as opening a log file)
B) Run loop doing process (process,wait cycle)
C) Clean up thread (such as closing a log file)What I was wondering was what was considered best practice with QThreads. I had 2 ideas.
The first is to do it in the run function. The logic here would look like
MyThread::run() { // Open log file // Set up process loop timer connect(timer, SIGNAL(timeout()), this, SLOT(Process())); timer->start(100); // Run loop exec(); // Close log file } MyThread::Process() { // Process and write to log file }
The second would be to use the started and finished slots
MyThread::MyThread(QObject *parent):QThread(parent) { connect(this,SIGNAL(started(),this,SLOT(StartThread())); connect(this,SIGNAL(finished(),this,SLOT(FinishThread())); } MyThread::StartThread() { // Open log file // Set up process loop timer connect(timer, SIGNAL(timeout()), this, SLOT(Process())); timer->start(100); } MyThread::FinishThread() { // Close log file } MyThread::Process() { // Process and write to log file }
Obviously one question would be if StartThread in the second option was in the context of the running thread. If not, then second option is a non-starter.
The point to either of these is too allow a process loop, so QThread::quit() can be used as needed. As noted above, the real question above, is which is considered the best practice?
Thanks,
Dale Pennington -
@DalePennington I would suggest a third approach: worker object. This way there is no need to subclass QThread.
See example here: https://doc.qt.io/qt-6/qthread.html -
The second one will not work as 'timer' lives in the main thread
-
@jsulm I was not looking at worker object first, because it seemed to add complications. Also it looks like doWork is called once, and keeps running to completion, so no time outs for any for event processing. Note that this thread would basically run from app start to app shutdown. So how could such an object for example receive an incoming signal ?
Dale
-
@DalePennington worker is an object. Connect worker with any widget or object which sends signals to worker.
-
I am clearly missing something about how the worker object really works. The example in the QThread documentation is incomplete. But it looks to me like the doWork, when kicked off, runs during completion, not performing a slice of work. I assume while doWork is executing, there is not event processing occuring on that thread, so no events delivered.
-
Compare this table to your needs:
A worker
QObject
can be controlled using signals that you emit from your main thread or object. In the same way you can get data back or even stop/restart your worker. -
@DalePennington said in Best QThread practices:
But it looks to me like the doWork, when kicked off, runs during completion, not performing a slice of work. I assume while doWork is executing, there is not event processing occuring on that thread, so no events delivered.
Correct.
Unless you do some explicit
processEvents()
yourself in the thread, which is no more advisable than doing so in the main thread event loop. Just saying for completeness. -
OK, I see the table saying use a worker object, but then JonB says that when doWork is running, I cannot get signals into the thread for processing. I assume signals can go out, but how do I get them in (for example, having a controlled shutdown on work rather than just quiting under it). Do I just have to treat it like a stock unix thread and have QMutexes and QWaitConditions around booleans to communcate into the processing thread ?
As a follow-on I now have worker pattern working. I did this by having doWork just initialize what I need and then use QTimers and Slots to cause breaks for events to show up. I am still having an issue on how to clean up when the main window is closed. I emit a signal to the worker but it does not get processed before the app shuts down. Since I want the cleanup to process within the context of the worker thread, how do I get it to cleanup on program exit ?
Thanks for the time
Dale -
@DalePennington said in Best QThread practices:
As a follow-on I now have worker pattern working. I did this by having doWork just initialize what I need and then use QTimers and Slots to cause breaks for events to show up.
No!
You don't need timers to "interrupt" your process.As far as I understand your initial idea of parsing/writing log files, you can create your object, let's say "LogFileWorker", and start it while passing a filename for example with a
start(filename)
signal connected to the worker slot which processes the file.
Once the worker is finished (or even in between) you can send results/signals to the main thread.
This whole procedure can be repeated whenever you want to and wherever you callworker->start
(or whatever you call your signal/function to init the start of your worker/thread).The main difference to your
QThread::run()
approach is that you do receive signals via yourQObject
worker that lives in a plain and simpleQThread
( Qt's thread wrapper class), which in turn does not require any further configurations.Edit:
In regards to proper cleanup, also read the article I've linked above in this post here
(it's also done through utilization of cross-thread signals callingQObject::deleteLater
for example) -
Actually what each thread is doing is getting input from a socket via a third party library (each thread is one "channel" which it its own socket). For now we are logging the data in a file, but later will be parsing some data and passing it along elsewhere in the app. So the thread runs constantly from start till the app is shut down.
Without the timer breakup, the process thread would never get any slots invoked as it would never give up processing in the thread. Also the cleanup issue I am concerned with is when the operator closes the app (for example via hitting the X button in the upper right corner). At that point I want each thread to close its log file so no data is lost. Since the app is being exited the deleteLater never occurs so the destructor is not called, so that is not a valid cleanup method (I put some debug prints to make sure this is the case).
-
I would say that initially you weren't fully on the wrong track. You just need to move everything out of QThread and into a worker object. If you are lazy it could even be just separate lambdas:
QThread *thread = new QThread(); connect(thread, &QThread::startet, []() { /* initialize */ }); connect(thread, &QThread::finished, []() { /* cleanup */ }); connect(thread, &QThread::finished, thread, &QThread::deleteLater); QMetaObject::invokeMethod(thread, []() { /* do work */ }); thread->start();
Notice the use of
QMetaObject::invokeMethod
to explicitly put a function call into the event loop of the thread. Using a worker object with slots instead of the lambdas is the cleaner approach.What you should never do is have an infinite loop for processing. Usually, there should be a trigger (i.e. a signal) to do another round of processing. However, sometimes you need to poll information in which case the timer could be a right choice. There is a trick with using a QTimer with timeout of 0 ms. This means that whenever the thread is idle the timer will timout. Note that "whenever the thread is idle" also means that one CPU core will be fully utilized constantly. Having a timeout of a few milliseconds can reduce that.
Another way with a worker object (which would also use up one CPU core) would be the following: Suppose you have a signal
WorkerObject::triggerProcessing()
which is connected to a slotWorkerObject::process()
. Instead ofvoid WorkerObject::process() { while(!done) { // do work } }
you can do this:
void WorkerObject::process() { if(!done) { // do work if(!done) //still not done emit triggerProcessing(); // do another round of processing } }
In this case you would launch the first round of processing using
QMetaObject::invokeMethod
onWorkerObject::process
.Without knowing what you want to achieve it is hard to tell which approach would be most appropriate in your case. The most important thing, though, is: Don't use an infinite loop (with a break condition) inside a running event loop.
-
That is basically the approach I am using. Only with a short timer to give up the core for a little bit. Messages average about 1/sec, but we want quick response as part of this effort is to do some timing analysis before creating the final app.
I had looked at finish early on as an early approach, but at that point I was looking for an "in-thread" use with a subclassed QThread, and the slot invocations in that case were in the parent thread, not wanted. However, using the Worker pattern, when the finished signal function is called, it is even in the worker thread, which appears to solve my problem for now.
Thanks,
Dale -