Communication between multiple threads that run at different frequencies
-
@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). -
@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. -
@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. -
I actually updated my code since i also asks similar question in stacks overflow (though still without solution). I'll show the important parts only:
class Window(QWidget): startSig = Signal() stopSig = Signal() def __init__(self): self.button.clicked.connect(self.startThr) self.button2.clicked.connect(self.stopThr) # I called the stop method (for the UI Thread) here self.button3.clicked.connect(self.showDlg) def startThr(self): if self.thread is None or not self.thread.isRunning(): self.thread = QThread() self.worker = Inlet_Worker() self.worker.moveToThread(self.thread) self.worker.data.connect(self.dlg.update) self.worker.tabs.connect(self.switch_tab) self.stopSig.connect(self.worker.stop) # I connect the signal here self.worker.finish.connect(self.finished) self.thread.started.connect(self.worker.starter) self.thread.start() def stopThr(self): self.stopSig.emit() # Emit stop signal
And here is the thread
class Inlet_Worker(QObject): data = Signal(int) tabs = Signal(int) finish = Signal() def __init__(self): super().__init__() self._stopped = False self._registered = False self.init_timers() self.c = 0 self.d = 0 def init_timers(self): self.timer1 = QTimer(self) self.timer1.timeout.connect(self.routine) self.timer2 = QTimer(self) self.timer2.timeout.connect(self.routine2) def starter(self): self.timer1.start(1000) self.timer2.start(2000) def routine(self): self.data.emit(self.c) self.c += 1 def routine2(self): self.tabs.emit(self.d) self.d += 1 @Slot() def stop(self): self.timer1.stop() self.timer2.stop() print('stopped') self.finish.emit()
The stop method in UI Thread emit signal
I connect the signal to the self.timer.stop() in the thread
I can upload the full code if necessary. -
@EvheMary said in Communication between multiple threads that run at different frequencies:
self.stopSig.connect(self.worker.stop)
Maybe you need explicetly define Qt::QueuedConnection (in C++ it is default for connections across threads).
-
@jsulm Oh wow, this also works. Unfortunately, I have already marked the previous answer as the solution but this works too. It seems that the default type for connect in pyqt is
Qt.AutoConnection
. Do you mind explaining why this happens when using AutoConnection while it works with queuedConnection? -
@EvheMary AutoConnection should be fine (it is also default in C++): Qt decides what to use. So, in case of connections across threads Qt uses QueuedConnection then. But in your case it looks like this is not happening, I don't know why. Maybe there are differences in PyQt.
-
AutoConnection
should connect- directly (synchronously) when object and caller live in the same thread.
- queued when object and caller live in different threads.
In C++ that can never go wrong. I am not a Python guru (repeating myself now). Maybe the explanation is that the connection type gets stuck in
DirectConnection
when the signal is connected before the second thread is being detached. In any case there's nothing wrong about explicitly usingQueuedConnection
.