Signaling threadpool finished without blocking UI + slot thread confusion
-
Hello,
I'm quite new to Qt and Pyside so I'd like to get some thoughts on my application.
In general I need to start several long running tasks and get informed that all work is done. It seems to me that
QThreadPool
withQRunnable
could be the right tool for that. It does not have any signal for the end of exectution of all threads so I probably need to start another thread withwaitForDone()
if I do not want to block my UI. Following the "QThread done right" I created following code. Can you please tell me if it is a correct Qt way how to do such thing?Infortunately the code has also strange issue with thread/slots. The slots
write()
andwrite2()
are fired in different threads based just on attribute type. If it isint
thenwrite2()
is not executed in MainThread but inself._thread
context. How can I control which thread is slot executed in? And why this depends on slot attribute type? (Bug in PySide?)Moreover when
self.edit
change is done from other thread than main one application segfaults. I thought that mutex should solve it but it's not the case...best regards
Janfrom PySide2 import QtCore, QtGui from PySide2.QtWidgets import * from PySide2.QtCore import * import random import time import threading class GenericWorker(QObject): def __init__(self, function, *args, **kwargs): super(GenericWorker, self).__init__() self.function = function self.args = args self.kwargs = kwargs result = Signal(object) finished = Signal() @Slot() def run(self): try: result = self.function(*self.args, **self.kwargs) except BaseException as ex: print(ex) else: self.result.emit(result) finally: self.finished.emit() class WorkerSignals(QObject): finished = Signal() error = Signal() result = Signal(object) progress = Signal(int) class Worker(QRunnable): def __init__(self, fn, *args, **kwargs): super(Worker, self).__init__() self.fn = fn self.args = args self.kwargs = kwargs self.signals = WorkerSignals() self.kwargs['progress_callback'] = self.signals.progress @Slot() def run(self): try: result = self.fn(*self.args, **self.kwargs) except BaseException as ex: print(ex) self.signals.error.emit() else: self.signals.result.emit(result) finally: self.signals.finished.emit() class Form(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("My Form") self.edit = QPlainTextEdit() self.button = QPushButton("Test") layout = QVBoxLayout() layout.addWidget(self.edit) layout.addWidget(self.button) self.setLayout(layout) self.threadpool = QtCore.QThreadPool() self.button.clicked.connect(self.run_threadpool) self.mtx = QMutex() def _write(self, text): self.mtx.lock() self.edit.moveCursor(QtGui.QTextCursor.End) self.edit.insertPlainText(text + f' {threading.current_thread().name}' + '\n') self.mtx.unlock() @Slot(str) # runs in MainThread def write(self, text=''): text = f'w {text} {threading.current_thread().name}' self._write(text) print(text) @Slot(int) # if the attribute is int, this does not run in MainThread def write2(self, i): text = f'w2 {i} {threading.current_thread().name}' self._write(text) print(text) def worker(self, id, *args, **kwargs): s = random.randint(1, 5) time.sleep(s) kwargs['progress_callback'].emit(id) return def _run_threadpool(self): print(f'_run_threadpool {threading.current_thread().name}') for i in range(5): w = Worker(self.worker, i) w.signals.result.connect(self.write) w.signals.progress.connect(self.write2) self.threadpool.start(w) self.threadpool.waitForDone() def run_threadpool(self): print(f'run_get_info {threading.current_thread().name}') self.edit.clear() self.edit.setDisabled(True) self._thread = QtCore.QThread() self._g_worker = GenericWorker(self._run_threadpool) self._g_worker.moveToThread(self._thread) self._thread.started.connect(self._g_worker.run) self._g_worker.finished.connect(lambda: self.edit.setDisabled(False)) self._g_worker.finished.connect(self._thread.quit) self._g_worker.finished.connect(self._g_worker.deleteLater) self._thread.finished.connect(self._thread.deleteLater) self._thread.start() if __name__ == '__main__': app = QApplication() form = Form() form.show() app.exec_()
output of this code is following:
run_get_info MainThread _run_threadpool Dummy-1 w None MainThread w None MainThread w None MainThread w None MainThread w None MainThread w2 0 Dummy-1 w2 1 Dummy-1
-
@denni-0 Sorry but we do not understand each other. You have pasted almost exactly the same code as I'm using and showed it in first post. You are right that there is a signal for end of each thread, but my question is about getting a signal when ALL threads from threadpool are done.QThreadPoll provides function
waitForDone()
so actually I'm firing another thread with this waiting and emiitting a signal at the end. It works, but I'd like to know if there is some other/better way.