Communication between multiple threads that run at different frequencies
-
My architecture would mirror what @jsulm wrote, except that I'd spawn short lived worker threads in the Qtimer callback for each function.
-
Thanks for the replies.
Just want to make clear, i want to create multiple threads, each with different purpose with flexible lifetime.- The 'stream in' thread will create 1 or 2 inlet objects at init, then run pull data (this function is the object's)
- Thread for the plot (after reviewing my code, this might not be needed, since i can just emit signal to the plotter function, the problem is that the pyqtgraph is in different qdialog, should i connect the signal when the qdialog is called or when i start the stream thread and is there any consequences when i emit signal but the slot is not yet created?)
- Process thread (same case with plot thread, but after reading kent's comment, i think its better to create short lived worker for every function call, or simpler: just qtimer and a function)
- Other thread
While i dont think the frequency doesn't affect the thread functions, it will affect the data that will be emitted (or the data in the object which i planned to share before)
After the first comment, i use the stream in thread to create multiple qtimer, each with their own purpose, but the problem i mentioned above appear (2)
Any suggestions is appreciated -
Thanks for the replies.
Just want to make clear, i want to create multiple threads, each with different purpose with flexible lifetime.- The 'stream in' thread will create 1 or 2 inlet objects at init, then run pull data (this function is the object's)
- Thread for the plot (after reviewing my code, this might not be needed, since i can just emit signal to the plotter function, the problem is that the pyqtgraph is in different qdialog, should i connect the signal when the qdialog is called or when i start the stream thread and is there any consequences when i emit signal but the slot is not yet created?)
- Process thread (same case with plot thread, but after reading kent's comment, i think its better to create short lived worker for every function call, or simpler: just qtimer and a function)
- Other thread
While i dont think the frequency doesn't affect the thread functions, it will affect the data that will be emitted (or the data in the object which i planned to share before)
After the first comment, i use the stream in thread to create multiple qtimer, each with their own purpose, but the problem i mentioned above appear (2)
Any suggestions is appreciatedIf problem (2) is what you write at point 2:
- Thread for the plot (after reviewing my code, this might not be needed
=> We don't have you code, so we can't say much.
since i can just emit signal to the plotter function, the problem is that the pyqtgraph is in different qdialog,
=> That's not a problem if they run in the same application
should i connect the signal when the qdialog is called or when i start the stream thread
Depends on your code: Signals should be connected before they are fired for the first time.
is there any consequences when i emit signal but the slot is not yet created?)
The information that the signal was fired will be lost. No buffering, no crashes etc - if that's what you mean.
-
Thanks for all the replies, i created a dummy demo of what my application is (I combine some codes from stacks). Unfortunately, there are some errors such as:
'QObject::~QObject: Timers cannot be stopped from another thread
This happens when i tried to start a new worker after stopping the previous one, o when i closed the app
Here is the code:import sys from PySide2.QtCore import * from PySide2.QtWidgets import * class Inlet_Worker(QObject): data = Signal(int) def __init__(self): super().__init__() self._stopped = False self._registered = False self.timer = QTimer(self) self.timer.timeout.connect(self.routine) self.c = 0 def starter(self): self.timer.start(1000) ''' def run(self): count = 0 self._stopped = False while not self._stopped: #if client.read_coils(address = 0x0802).bits[0]: count += 1 if count % 20 == 0 and not self._registered: self.updateBarcodeRegistration(True) self.timer.start(2000) QCoreApplication.processEvents() QThread.msleep(100) self.updateBarcodeRegistration(False) self.timer.stop() print('Stopped') ''' def routine(self): print(self.c) self.data.emit(self.c) self.c += 1 def stop(self): self.timer.stop() print('stopped') class Window(QWidget): def __init__(self): super().__init__() self.dlg = Dialog() self.thread = QThread() self.button = QPushButton('Start') self.button2 = QPushButton('Stop') self.button3 = QPushButton('Window') layout = QHBoxLayout(self) layout.addWidget(self.button) layout.addWidget(self.button2) layout.addWidget(self.button3) self.button.clicked.connect(self.startThr) self.button2.clicked.connect(self.stopThr) self.button3.clicked.connect(self.showDlg) def showDlg(self): if not self.dlg.isVisible(): self.dlg.show() def startThr(self): self.worker = Inlet_Worker() self.worker.moveToThread(self.thread) self.worker.data.connect(self.dlg.update) self.thread.started.connect(self.worker.starter) self.thread.start() def stopThr(self): self.worker.stop() self.thread.terminate() self.thread.wait() def closeEvent(self, event): self.worker.stop() self.thread.quit() self.thread.wait() class Dialog(QWidget): def __init__(self): super().__init__() self.text1 = QLabel("Label 1 : ") self.text2 = QLabel("Text2") layout = QHBoxLayout(self) layout.addWidget(self.text1) layout.addWidget(self.text2) self.setGeometry(400, 100, 100, 50) def update(self, sig): self.text2.setText(str(sig)) def closeEvent(self, event): pass if __name__ == '__main__': app = QApplication(sys.argv) window = Window() window.setGeometry(750, 100, 200, 50) window.show() sys.exit(app.exec_())
Can you explain why is this happening? Thanks in advance.
-
The timer is living in one thread and its
stop()
slot is called directly from another thread.
Don't want the neighbor entering my flat without ringing the door bell, just to turn the music louder (or lower) - right?I am not a python guru, but something like
QMetaObject.invokeMethod(self.timer, 'stop', Qt.QueuedConnection)
should do whatself.timer.stop()
doesn't when the timer lives elswhere.
QueuedConnection
doesn't wait for the timer to be stopped. You can use other connection types if required. -
Thanks a lot for the solution. It fixed the error. It seems I misunderstood what a Qtimer is. QTimer might also be a threading interface, hence why I can't just call its start and stop methods. I'm just still confused about why I need this method to stop the timer, but I can do it directly for the start method?
And is QMetaObject works similarly with what a signal is? -
Thanks a lot for the solution. It fixed the error. It seems I misunderstood what a Qtimer is. QTimer might also be a threading interface, hence why I can't just call its start and stop methods. I'm just still confused about why I need this method to stop the timer, but I can do it directly for the start method?
And is QMetaObject works similarly with what a signal is?@EvheMary said in Communication between multiple threads that run at different frequencies:
QTimer might also be a threading interface
What does this mean? QTimer is just a timer and has nothing to do with threads.
"why I can't just call its start and stop" - because QTimer is not thread safe (only reentrant as documentation states), so you should not call its methods from other threads than the thread where the QTimer instance is living. -
Thanks a lot for the solution. It fixed the error. It seems I misunderstood what a Qtimer is. QTimer might also be a threading interface, hence why I can't just call its start and stop methods. I'm just still confused about why I need this method to stop the timer, but I can do it directly for the start method?
And is QMetaObject works similarly with what a signal is?@EvheMary
@Axel-Spoerl may well know more than I, but I have never had to useQMetaObject.invokeMethod()
. Fine if that's good/safe/the right to do this/works for you. But my first thought would have been to send a signal from wherever to the thread which the thread has a slot on and does its ownself.timer.stop()
when received, if that is what you are trying to achieve. Am I wrong? -
my first thought would have been to send a signal
As usual, @JonB , your (first) thoughts are excellent!
QMetaObject.invokeMethod()
spares the definition of a signal, especially when you call a void slot without arguments, likeQTimer::stop()
. It can also be quite handy if you call it with theBlockingConnection
argument, in which case you can block'n'wait for a return value :-) -
my first thought would have been to send a signal
As usual, @JonB , your (first) thoughts are excellent!
QMetaObject.invokeMethod()
spares the definition of a signal, especially when you call a void slot without arguments, likeQTimer::stop()
. It can also be quite handy if you call it with theBlockingConnection
argument, in which case you can block'n'wait for a return value :-)@Axel-Spoerl
Totally respect your answer. I have seen mentions ofQMetaObject.invokeMethod()
on the web, but I know it's used in "advanced" situations (including a lot from Python/PyQt/PySide, probably understandably) so haven't touched it! I can tell that from the'stop'
argument being a literal string it's going to look up the method to call by name ("reflection"), like the oldSIGNAL
/SLOT()
macros approach did; so being more a C++ purist I would tend to avoid that, preferring compile-time safety/argument checking etc. -
@Axel-Spoerl
Totally respect your answer. I have seen mentions ofQMetaObject.invokeMethod()
on the web, but I know it's used in "advanced" situations (including a lot from Python/PyQt/PySide, probably understandably) so haven't touched it! I can tell that from the'stop'
argument being a literal string it's going to look up the method to call by name ("reflection"), like the oldSIGNAL
/SLOT()
macros approach did; so being more a C++ purist I would tend to avoid that, preferring compile-time safety/argument checking etc.@JonB
Totally fair point! A kitten dies, each time you resolve a symbol with a string search at run time....in Python.
There are moreinvokeMethod()
overloads for the C++ lovers from 6.4 onward. -
@JonB
Totally fair point! A kitten dies, each time you resolve a symbol with a string search at run time....in Python.
There are moreinvokeMethod()
overloads for the C++ lovers from 6.4 onward.@Axel-Spoerl said in Communication between multiple threads that run at different frequencies:
A kitten dies, each time you resolve a symbol with a string search at run time....in Python.
:)
-
Thanks for all these comments. I might not fully understand since I'm still a beginner in qt, so sorry if I mentioned something wrong. I thought that since I use a signal to communicate with the Qtimer thread it should be safe. And I should be calling the stop method from the thread that it was created. Is my implementation of the signals and slot not correct?
my understanding of using the signal and slot is :
UI Thread --> send signal --> stop method (slot) at another thread --> QTimer stop -
Thanks for all these comments. I might not fully understand since I'm still a beginner in qt, so sorry if I mentioned something wrong. I thought that since I use a signal to communicate with the Qtimer thread it should be safe. And I should be calling the stop method from the thread that it was created. Is my implementation of the signals and slot not correct?
my understanding of using the signal and slot is :
UI Thread --> send signal --> stop method (slot) at another thread --> QTimer stop -
@JonB But if i call
self.worker.stop()
that callsself.timer.stop()
from the UI Thread, the errorQObject::~QObject: Timers cannot be stopped from another thread
appears and that's what I want to know what causes it and how to remove it. If i changeself.timer.stop()
withQMetaObject.invokeMethod(self.timer, 'stop', Qt.AutoConnection)
the error don't appear. -
@JonB But if i call
self.worker.stop()
that callsself.timer.stop()
from the UI Thread, the errorQObject::~QObject: Timers cannot be stopped from another thread
appears and that's what I want to know what causes it and how to remove it. If i changeself.timer.stop()
withQMetaObject.invokeMethod(self.timer, 'stop', Qt.AutoConnection)
the error don't appear.@EvheMary said in Communication between multiple threads that run at different frequencies:
that's what I want to know what causes it
Your worker object lives in another thread. So, if you call self.worker.stop() in UI thread stop() will be executed in UI thread and it will also call self.timer.stop() in UI thread. But self.timer also lives in the worker thread, so you get that warning (QTimer is not thread safe).
To avoid this you should NOT call self.worker.stop() directly from the UI thread but instead either use invokeMethod() or connect self.worker.stop() to a signal in your UI and emit this signal to stop the worker. -
@jsulm Yes. Thanks for the answer. Now I know that calling method in another thread from the UI thread is not good. But I don't know why when I connect a signal from UI Thread to the
self.worker.stop()
, the error persists. It will only be gone when I useinvokeMethod()
instead ofself.timer.stop()
, which leads to why I suspect QTimer to be another threading interface (because I need to send another signal to the timer itself). -
@jsulm Yes. Thanks for the answer. Now I know that calling method in another thread from the UI thread is not good. But I don't know why when I connect a signal from UI Thread to the
self.worker.stop()
, the error persists. It will only be gone when I useinvokeMethod()
instead ofself.timer.stop()
, which leads to why I suspect QTimer to be another threading interface (because I need to send another signal to the timer itself).@EvheMary said in Communication between multiple threads that run at different frequencies:
But I don't know why when I connect a signal from UI Thread to the self.worker.stop()
Can you show how you did the connection?
-
Adding to @jsulm :
what causes it
Theoretically, two different threads could call the
QTimer
'sstop()
method at the same time. Or, even worse, one of them callsstart()
.
The warning is triggered, if a slot is called from a thread different from the timer's living environment.
Emitting a signal or callinginvokeMethod()
makes sure that concurrent calls are properly serialized, posted into the timer's event loop and executed within the timer's thread. Such behaviour is notQTimer
specific. It applies to most Qt classes, check this for more information. -
@JonB But if i call
self.worker.stop()
that callsself.timer.stop()
from the UI Thread, the errorQObject::~QObject: Timers cannot be stopped from another thread
appears and that's what I want to know what causes it and how to remove it. If i changeself.timer.stop()
withQMetaObject.invokeMethod(self.timer, 'stop', Qt.AutoConnection)
the error don't appear.@EvheMary said in Communication between multiple threads that run at different frequencies:
@JonB But if i call self.worker.stop() that calls self.timer.stop() from the UI Thread, the error QObject::~QObject: Timers cannot be stopped from another thread appears and that's what I want to know what causes it and how to remove it.
That is precisely what I explained: you call
self.timer.stop()
from the UI Thread, that is not the thread where the timer lives, hence the explicit error message telling you what is wrong.But I don't know why when I connect a signal from UI Thread to the self.worker.stop(), the error persists.
As @jsulm said, show your
connect()
statement for this.