QThread vs QRunnable
-
Hello everyone!
Reading aboutQRunnable
, I understand that they don't support signals/slots. However I found this https://www.pythonguis.com/tutorials/multithreading-pyqt-applications-qthreadpool/, where the author creates a customWorkerSignals
object that inherits fromQObject
and defines the signals there.
Is it ok to use this pattern also for aQThreadPool
, so that, in a module you define the ThreadPool and the Workers and communicate with the mainprocess throughQThreadPool
's signals?
I.e. aQThreadPool
would be created and initialized in the main thread, it will create a Worker (QRunnable
) for every task needed, theWorker
will emit a signal on finish that will be connected to a QThreadPool method, that in turn emit a signal through its own customThreadPoolSignal
(QObject
). This way the main thread will be notified by theQThreadPool
that was created in the first step.
Notifying theMainWindow
results in that all UI components will be notified only throughMainWindow
that is acting as a Controller.
An example would be the following:import sys from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import QApplication, QMainWindow class WorkerSignals(QObject): result = pyqtSignal(str) class Worker(QRunnable): def __init__(self): super().__init__() self.signals = WorkerSignals() @pyqtSlot() def run(self): self.signals.result.emit('test') class PoolSignals(QObject): result = pyqtSignal(str) class ThreadPool(QThreadPool): def __init__(self): super().__init__() self.signal = PoolSignals() def spawn_workers(self): worker = Worker() worker.signals.result.connect(self.emit_worker_output) self.start(worker) def emit_worker_output(self, output): self.signal.result.emit(output) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.thread_pool = ThreadPool() self.register_actions() self.do_work() def register_actions(self): self.thread_pool.signal.result.connect(self.print_output) def do_work(self): for i in range(0, 10): self.thread_pool.spawn_workers() @pyqtSlot(str) def print_output(self, text): print(text) def main(): app = QApplication(sys.argv) main_window = MainWindow() sys.exit(app.exec_()) if __name__ == '__main__': main()
Is this pattern acceptable, or an overkill and I should move to
QThread
s that natively support signals? -
Hi,
What kind of work are your QRunnable going to do ?
-
Sounds like QtConcurrent::run already provides what you need with the help of QFutureWatcher
-
Sounds like QtConcurrent::run already provides what you need with the help of QFutureWatcher
-
Sorry, my bad, at some point I lost the fact that you are coding in Python. So no, you did not miss anything.
See this stack overflow answer. There's a link for a Python implementation in the comments that could be of use to you.
-
Thank you very much !
You think I should go with https://gist.github.com/ales-erjavec/5ef43c5b3907a9e4924eec09bfb9d9c6, than byQThreadPool
using the implementation I posted in my initial post?
Also what aboutQThread
? Is there any implementation I could use to limit the maximum threads being used to reproduce somehow the functionality ofQThreadPool
maybe? Or is it safe just useQThread
s ? -
For your use case, QThreadPool would be more indicated as it will do the QThread handling for you (i.e. queue your tasks if you start more of them than available threads).
However, since you are using signals and slots for these tasks, I would recommend you to create your own class for handling the results rather than customizing QThreadPool so you keep a clean separation of concerns.
-
This class handling the events, should support signaling though, right?
From what I understand this partself.signal = PoolSignals()
is what you don't like in myThreadPool
, yes? However,I would need a class likePoolSignals
to handle the events and signal the main thread, correct?Please forgive my ignorance!
-
If you want to be really clean, you don't use an external object to generate signals. Signals shall be emitted from within the class that they are declared in.
For example in your case, the task class should inherit from QObject and QRunnable. Then you can directly declare/emit the signals.
There's no need for your ThreadPoolSignal object as QThreadPool is derived from QObject.
-
If you want to be really clean, you don't use an external object to generate signals. Signals shall be emitted from within the class that they are declared in.
For example in your case, the task class should inherit from QObject and QRunnable. Then you can directly declare/emit the signals.
There's no need for your ThreadPoolSignal object as QThreadPool is derived from QObject.
@SGaist said in QThread vs QRunnable:
If you want to be really clean, you don't use an external object to generate signals. Signals shall be emitted from within the class that they are declared in.
For example in your case, the task class should inherit from QObject and QRunnable. Then you can directly declare/emit the signals.
There's no need for your ThreadPoolSignal object as QThreadPool is derived from QObject.
I used the following :
import sys from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import QApplication, QMainWindow class WorkerSignals(QObject): result = pyqtSignal(str) class Worker(QRunnable): def __init__(self): super().__init__() self.signals = WorkerSignals() @pyqtSlot() def run(self): self.signals.result.emit('test') class ThreadPool(QThreadPool): result = pyqtSignal(str) def __init__(self): super().__init__() def spawn_workers(self): worker = Worker() worker.signals.result.connect(self.emit_worker_output) self.start(worker) def emit_worker_output(self, output): self.result.emit(output) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.thread_pool = ThreadPool() self.register_actions() self.do_work() def register_actions(self): self.thread_pool.result.connect(self.print_output) def do_work(self): for i in range(0, 10): self.thread_pool.spawn_workers() @pyqtSlot(str) def print_output(self, text): print(text) def main(): app = QApplication(sys.argv) main_window = MainWindow() sys.exit(app.exec_()) if __name__ == '__main__': main()
which works great! I didn't use multiple inheritance for now as this makes me feel a bit nervous!
If you be so kind and let me know what you think of the code above, would be greatly appreciated! -
You don't need to be nervous, multiple inheritance is supported in Python as well. The only thing you have to take into account is that QObject must be the first in the list.