为什么pyqtsignal无法正常触发?

问题描述 投票:0回答:1

我是 pyqt 新手,我在应用程序中使用 pyqtsignal,但不知道为什么它不能正常工作。我不明白为什么会发生这种情况,程序中的其余 pyqtsignal 工作正常,我想知道如果我遗漏了什么?

当我单击停止按钮时,程序不会停止。 如果我直接调用runner.stop(),程序可以正常退出。

所以我很困惑。下面是我的代码:


class Runner(QObject):
    stopped = pyqtSignal()

    def __init__(self, name):
        super().__init__()
        self.name = name
        self.run_flag = True

    def start(self):
        print("start", self.run_flag)
        while self.run_flag:
            self.timeoutSlot()
            time.sleep(2)

    def stop(self):
        logging.warning("stop called.")
        self.run_flag = False
        self.stopped.emit()

    def timeoutSlot(self):
        # long time work
        time.sleep(10)
        logging.info(f"{self.name} is running")


class MainWindow(QMainWindow):
    stopSignal = pyqtSignal()

    def __init__(self):
        super().__init__()
        self._central_widget = QWidget()
        self._central_layout = QVBoxLayout()
        self.start_btn = QPushButton("start")
        self.stop_btn = QPushButton("stop")
        self._central_layout.addWidget(self.start_btn)
        self._central_layout.addWidget(self.stop_btn)
        self._central_widget.setLayout(self._central_layout)
        self.setCentralWidget(self._central_widget)
        self.start_btn.clicked.connect(self.start)
        self.stop_btn.clicked.connect(self.stop)

        self.connects = []
        self.runners = []

    def start(self):
        for i in range(2):
            setattr(self, f"thread_{i}", QThread())
            setattr(self, f"runner_{i}", Runner(f"runner_{i}"))
            thread = getattr(self, f"thread_{i}")
            runner = getattr(self, f"runner_{i}")
            assert isinstance(thread, QThread)
            assert isinstance(runner, Runner)
            runner.moveToThread(thread)
            thread.started.connect(runner.start)

            conn = self.stopSignal.connect(runner.stop)
            self.connects.append(conn)

            runner.stopped.connect(thread.quit)
            runner.stopped.connect(runner.deleteLater)
            self.runners.append(runner)
            thread.finished.connect(thread.deleteLater)
            thread.start()

    def stop(self):
        # blow can work normally
        # for runner in self.runners:
        #     runner.stop()
        # self.runners = []
        print("stop btn clicked!")
        
        # can't work
        self.stopSignal.emit()
        # for conn in self.connects:
        #     self.stopSignal.disconnect(conn)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MainWindow()
    win.show()
    sys.exit(app.exec())
python qt pyqt
1个回答
0
投票

信号确实有效,问题是由于它连接到一个线程,所以它的连接是一个

QueuedConnection
。这意味着只有在接收器线程允许的情况下才会调用连接的插槽。

您已将线程的

started
信号连接到
start
函数,该函数立即运行阻塞循环,防止 QThread 事件循环继续进行,直到该函数将控制权返回给它。由于排队信号只能由其事件循环处理,因此这种情况永远不会发生。

针对这种具体情况,有两种可能的解决方案:

  1. 直接调用各个runner的
    stop()
    函数;
  2. 使用
    DirectConnection
    代替
    stopSignal

在第一种情况下,只需迭代所有跑步者:

    def stop(self):
        for runner in self.runners:
            runner.stop()

或者:

    def start(self):
        for i in range(2):
            ...
            conn = self.stopSignal.connect(runner.stop, Qt.DirectConnection)
            ...

请注意,您对

getattr
setattr
的使用不必要地复杂:只需创建一个局部变量,然后调用
setattr
,然后删除
assert
,这没有任何意义,因为您刚刚创建了这些对象。

此外,假设您始终具有持久引用,当再次调用

start()
时,这些引用可能会被覆盖,从而导致线程在运行时被销毁。

更好的方法是对线程和运行器使用列表,以便您最终可以再次迭代现有对象并决定要做什么(重新启动线程、停止线程等)。

© www.soinside.com 2019 - 2024. All rights reserved.