Threading with QRunnable - Proper manner of sending bi-directional callbacks

前端 未结 1 2005
囚心锁ツ
囚心锁ツ 2021-01-20 05:46

From a tutorial, I\'ve seen that signals and slots can be used to make callbacks from a worker thread into the main GUI thread, but I\'m uncertain how to establish bi-direct

相关标签:
1条回答
  • 2021-01-20 06:19

    To understand the cause of the error you must run the code in the terminal and you will get the following error message:

    QObject::connect: Cannot connect MainWindow::mainthread_callback_to_worker() to (nullptr)::acknowledge_callback_in_worker()
    Traceback (most recent call last):
      File "main.py", line 72, in thread_example
        self.mainthread_callback_to_worker.connect(worker.acknowledge_callback_in_worker) # <-- causes crash
    TypeError: connect() failed between MainWindow.mainthread_callback_to_worker[] and acknowledge_callback_in_worker()
    Aborted (core dumped)
    

    And the cause of the error is the abuse of the pyqtSlot decorator since it should only be used in the QObject methods but QRunnable is not causing that exception, in addition that in a non-QObject it does not take any advantage so that decorator in the run() method doesn't make sense.

    On the other hand, a QRunnable is only an interface that lives in the main thread and only the run method is executed in another thread, so a QRunnable cannot be a worker since that type of objective must execute its methods in a secondary thread.

    So with the above QRunnable is not the appropriate option, so for your purpose I recommend using a QObject that lives in a secondary thread and invoking the methods.

    import sys
    import time
    
    from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QTimer, QThread
    from PyQt5.QtWidgets import (
        QApplication,
        QMainWindow,
        QWidget,
        QVBoxLayout,
        QLabel,
        QPushButton,
    )
    
    
    class Worker(QObject):
        callback_from_worker = pyqtSignal()
    
        def __init__(self, func, *args, **kwargs):
            super(Worker, self).__init__()
            self._func = func
            self.args = args
            self.kwargs = kwargs
            self.kwargs["signal"] = self.callback_from_worker
    
        def start_task(self):
            QTimer.singleShot(0, self.task)
    
        @pyqtSlot()
        def task(self):
            self._func(*self.args, **self.kwargs)
    
        @pyqtSlot()
        def acknowledge_callback_in_worker(self):
            print("Acknowledged Callback in Worker")
            print(threading.current_thread())
    
    
    class MainWindow(QMainWindow):
        mainthread_callback_to_worker = pyqtSignal()
    
        def __init__(self, *args, **kwargs):
            super(MainWindow, self).__init__(*args, **kwargs)
    
            w, lay = QWidget(), QVBoxLayout()
            w.setLayout(lay)
            self.setCentralWidget(w)
            self.timer_label = QLabel("Timer Label")
            lay.addWidget(self.timer_label)
            self.btn_thread_example = QPushButton("Push Me")
            self.btn_thread_example.pressed.connect(self.thread_example)
            lay.addWidget(self.btn_thread_example)
    
            self.counter = 0
            self.timer = QTimer(interval=1000, timeout=self.recurring_timer)
            self.timer.start()
    
            self._worker = Worker(self.do_something)
            self._worker.callback_from_worker.connect(
                self.acknowledge_callback_in_mainthread_and_respond
            )
    
            self.worker_thread = QThread(self)
            self.worker_thread.start()
            self._worker.moveToThread(self.worker_thread)
    
        @pyqtSlot()
        def do_something(self, signal):
            print(
                "do_something is sleeping briefly. Try to see if you get a locked widget..."
            )
            time.sleep(7)
            signal.emit()
    
        @pyqtSlot()
        def acknowledge_callback_in_mainthread_and_respond(self):
            print("Acknowledged Callback in Main")
            self.mainthread_callback_to_worker.emit()
    
        def thread_example(self):
            print("Beginning thread example")
            self._worker.start_task()
    
        def recurring_timer(self):
            self.counter += 1
            self.timer_label.setText(f"Counter: {self.counter}")
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        win = MainWindow()
        app.setStyle("Fusion")
        win.show()
        ret = app.exec_()
        win.worker_thread.quit()
        win.worker_thread.wait()
        sys.exit(ret)
    
    0 讨论(0)
提交回复
热议问题