In Qt/PyQt, I used to make threading using a Worker class and a QThread.
self.worker = Worker()
self.thread = QThread()
worker.moveToThread(thread)
setup_signal_slot_with_main_object()
// start
thread.start()
I must place setup_signal_slot_with_main_object() after moveToThread(). But I have a complex worker. In Worker.__ init__(), it creates many QObjects and connects internal signals and slots. I don't want to create a method which makes all connections and calls worker.setup_signal_slot() after worker->moveToThread(&thread) because Worker contains many child QObjects and each QObject can make signal/slot in its constructor.
In Qt/C++, I can make signal/slot connection in worker's constructor. But in PyQt, slot will not run in new thread.
This is an example with a Worker contains a QTimer
import sys
import signal
import threading
from PyQt5.QtCore import QObject, pyqtSignal, QTimer, QCoreApplication, QThread
import datetime
class Worker(QObject):
timeChanged = pyqtSignal(object)
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.timer = QTimer(self)
self.timer.setInterval(1000)
# I want to make connection at here
self.timer.timeout.connect(self.main_process)
def start(self):
# self.timer.timeout.connect(self.main_process)
self.timer.start()
print('Worker thread {}: Start timer'.format(threading.get_ident()))
# this method still run in main thread
def main_process(self):
timestamp = datetime.datetime.now()
print('Worker thread {}: {}'.format(threading.get_ident(), timestamp.strftime('%d-%m-%Y %H-%M-%S')))
self.timeChanged.emit(timestamp)
class WorkerThread(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.emitter = Worker()
self.thread = QThread(self)
self.emitter.moveToThread(self.thread)
self.thread.started.connect(self.emitter.start)
self.thread.finished.connect(self.emitter.deleteLater)
self.emitter.timeChanged.connect(self.show_time)
def start(self):
self.thread.start()
def stop(self):
if self.thread.isRunning():
self.thread.quit()
self.thread.wait()
print('Exit thread')
def show_time(self, timestamp):
print('Main thread {}: {}'.format(threading.get_ident(), timestamp.strftime('%d-%m-%Y %H-%M-%S')))
def signal_handler(sig, frame):
print('Quit')
app.quit()
if __name__ == '__main__':
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
app = QCoreApplication(sys.argv)
timer = QTimer()
timer.timeout.connect(lambda: None)
timer.start(500)
print('Main thread {}'.format(threading.get_ident()))
emitter = WorkerThread()
emitter.start()
sys.exit(app.exec_())
In Worker, timer timeout will call main_process in main thread. I can move self.timer.timeout.connect(self.main_process) into method worker.start(). But as I have said above, I still want to place internal signal/slot in its constructor. Could anyone suggest me a solution ? Thanks !
If you want the methods to be invoked in the same thread where the receiver uses the pyqtSlot()
decorator, if you don't do it then it will be called in the sender's thread.
import sys
import signal
import threading
import datetime
from PyQt5.QtCore import QObject, pyqtSignal, QTimer, QCoreApplication, QThread, pyqtSlot
class Worker(QObject):
timeChanged = pyqtSignal(object)
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.timer = QTimer(self)
self.timer.setInterval(1000)
self.timer.timeout.connect(self.main_process)
@pyqtSlot()
def start(self):
self.timer.start()
print("Worker thread {}: Start timer".format(threading.get_ident()))
@pyqtSlot()
def main_process(self):
timestamp = datetime.datetime.now()
print(
"Worker thread {}: {}".format(
threading.get_ident(), timestamp.strftime("%d-%m-%Y %H-%M-%S")
)
)
self.timeChanged.emit(timestamp)
class WorkerThread(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.emitter = Worker()
self.thread = QThread(self)
self.emitter.moveToThread(self.thread)
self.thread.started.connect(self.emitter.start)
self.thread.finished.connect(self.emitter.deleteLater)
self.emitter.timeChanged.connect(self.show_time)
@pyqtSlot()
def start(self):
self.thread.start()
def stop(self):
if self.thread.isRunning():
self.thread.quit()
self.thread.wait()
print("Exit thread")
@pyqtSlot(object)
def show_time(self, timestamp):
print(
"Main thread {}: {}".format(
threading.get_ident(), timestamp.strftime("%d-%m-%Y %H-%M-%S")
)
)
def signal_handler(sig, frame):
print("Quit")
app.quit()
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
app = QCoreApplication(sys.argv)
timer = QTimer()
timer.timeout.connect(lambda: None)
timer.start(500)
print("Main thread {}".format(threading.get_ident()))
emitter = WorkerThread()
emitter.start()
sys.exit(app.exec_())
Output:
Main thread 140175719339648
Worker thread 140175659480832: Start timer
Worker thread 140175659480832: 26-07-2019 04-39-42
Main thread 140175719339648: 26-07-2019 04-39-42
Worker thread 140175659480832: 26-07-2019 04-39-43
Main thread 140175719339648: 26-07-2019 04-39-43
Worker thread 140175659480832: 26-07-2019 04-39-44
Main thread 140175719339648: 26-07-2019 04-39-44
Worker thread 140175659480832: 26-07-2019 04-39-45
Main thread 140175719339648: 26-07-2019 04-39-45