Qt Forum

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Unsolved

    Unsolved Signaling threadpool finished without blocking UI + slot thread confusion

    Qt for Python
    2
    3
    2081
    Loading More Posts
    • 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.
    • xhpohanka
      xhpohanka last edited by xhpohanka

      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 Reply Quote 0
      • xhpohanka
        xhpohanka last edited by

        @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 Reply Quote 0
        • xhpohanka
          xhpohanka last edited by

          @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 Reply Quote 0
          • First post
            Last post