Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Signaling threadpool finished without blocking UI + slot thread confusion

Signaling threadpool finished without blocking UI + slot thread confusion

Scheduled Pinned Locked Moved Unsolved Qt for Python
3 Posts 2 Posters 2.8k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • xhpohankaX Offline
    xhpohankaX Offline
    xhpohanka
    wrote on last edited by xhpohanka
    #1

    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 with QRunnable 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 with waitForDone() 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() and write2() are fired in different threads based just on attribute type. If it is int then write2() is not executed in MainThread but in self._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
    Jan

    from 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
    
    1 Reply Last reply
    0
    • xhpohankaX Offline
      xhpohankaX Offline
      xhpohanka
      wrote on last edited by
      #2

      @denni-0 I updated the wording of my question a bit, maybe it is now more understandable. I want to get a signal when all threads from threadpool were finished...

      1 Reply Last reply
      0
      • xhpohankaX Offline
        xhpohankaX Offline
        xhpohanka
        wrote on last edited by
        #3

        @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.

        1 Reply Last reply
        0

        • Login

        • Login or register to search.
        • First post
          Last post
        0
        • Categories
        • Recent
        • Tags
        • Popular
        • Users
        • Groups
        • Search
        • Get Qt Extensions
        • Unsolved