QThread 似乎没有启动; PyQt5,Python 2.7.9

QThread doesn't appear to start; PyQt5, Python 2.7.9

摘要 PyQt5 似乎没有创建对应于 QThread 对象的新线程,或者我没有正确建立 Slot/Signal 链接。请帮我找出我的问题。


我是 Python 的一个相对随意的用户,但我被要求为另一个团队创建一个实用程序,将他们的一些 Python 库(它们本身包装 C++)包装在一个GUI。因为这个实用程序是为另一个团队使用的,所以我不能更改编译器等版本,或者至少不能在没有提供正当理由的情况下更改。 该实用程序旨在提供一个接口,用于调试我的同事正在开发的某些硬件。

检查选项后,我决定使用 Qt 和 PyQt 绑定。我遵循的步骤是:

  1. 安装Visual Studio 2010 SP1(必需,因为其他团队的库是使用此版本的 MS 编译器编译的)。
  2. 安装Python 2.7.9(他们的Python版本)
  3. 安装qt-opensource-windows-x86-msvc2010-5.2.1.exe
  4. 获取 SIP-4 的源代码。18.zip 并编译和安装
  5. 获取 PyQt-gpl-5.2 的源代码。1.zip,编译并安装
  6. 尝试构建一个包装其他团队的通信和翻译库的 PyQt 应用程序。据我所知,这些库不是异步的,所以我认为我需要将应用程序的那部分与 GUI.
  7. 分开

我编写的代码生成了 UI 并且在某种意义上是响应式的,如果我在从 QAction 对象调用的方法中放置断点,那么这些断点就会被适当地触发。我的问题是,我创建的 Worker 对象似乎没有移动到单独的线程(尽管调用了 moveToThread),因为如果我建立类型为 BlockingQueuedConnection 而不是 QueuedConnection 的连接,则会出现死锁。我在 Worker 类型的插槽上设置的断点从未被触发。

这是代码::

import os
import sys
import time

from PyQt5.QtWidgets import QMainWindow, QTextEdit, QAction, QApplication, QStatusBar, QLabel, QWidget, QDesktopWidget, QInputDialog
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt, QThread, QObject, pyqtSignal, pyqtSlot


class Worker(QObject):

    def __init__(self):
        super(Worker, self).__init__()
        self._isRunning = True
        self._connectionId = ""
        self._terminate = False

    @pyqtSlot()
    def cmd_start_running(self):
        """This slot is used to send a command to the HW asking for it to enter Running mode.
        It will actually work by putting a command in a queue for the main_loop to get to
        in its own serialised good time.  All the other commands will work in a similar fashion
        Up until such time as it is implemented, I will fake it."""
        self._isRunning = True
        pass

    @pyqtSlot()
    def cmd_stop_running(self):
        """This slot is used to send a command to the HW asking for it to enter Standby mode.
        Up until such time as it is implemented, I will fake it."""
        self._isRunning = False

    @pyqtSlot()
    def cmd_get_version(self):
        """This slot is used to send a command to the HW asking for its version string"""
        pass

    @pyqtSlot()
    def cmd_terminate(self):
        """This slot is used to notify this object that it has to join the main thread."""
        pass

    @pyqtSlot()
    def main_loop(self):
        """This slot is the main loop that is attached to the QThread object.  It has sleep periods
        that allow the messages on the other slots to be processed."""
        while not self._terminate:
            self.thread().sleep(1)
            # While there is stuff on the wire, get it off, translate it, then
            # signal it
            # For the mean while, pretend that _isRunning corresponds to when
            # RT streams will be
            # being received from the HW.
            if self._isRunning:
                pass
            # Search queue for commands, if any found, translate, then put on
            # the wire

class DemoMainWindow(QMainWindow):

    sgnl_get_version = pyqtSignal()
    sgnl_start_running = pyqtSignal()
    sgnl_stop_running = pyqtSignal()
    sgnl_terminate = pyqtSignal()


    def __init__(self):
        super(DemoMainWindow, self).__init__()
        self.initUI()

        self._workerObject = Worker()
        self._workerThread = QThread()
        self._workerObject.moveToThread(self._workerThread)
        self._workerThread.started.connect(self._workerObject.main_loop, type=Qt.QueuedConnection)


        # I changed the following connection to type BlockingQueuedConnection,
        # and got a Deadlock error
        # reported, so I assume that there is already a problem before I get to
        # this point.
        # I understand that the default for 'type' (Qt.AutoConnection) is
        # supposed to correctly infer that a QueuedConnection is required.
        # I was getting desperate.
        self.sgnl_get_version.connect(self._workerObject.cmd_get_version, type=Qt.QueuedConnection)
        self.sgnl_start_running.connect(self._workerObject.cmd_start_running, type=Qt.QueuedConnection)
        self.sgnl_stop_running.connect(self._workerObject.cmd_stop_running, type=Qt.QueuedConnection)
        self.sgnl_terminate.connect(self._workerObject.cmd_terminate, type=Qt.QueuedConnection)



    def initUI(self):

        textEdit = QTextEdit()
        self.setCentralWidget(textEdit)
        lbl = QLabel(self.statusBar())
        lbl.setText("HW Version:   ")
        self.statusBar().addPermanentWidget(lbl)

        exitAction = QAction(QIcon('exit24.png'), 'Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.close)

        connectAction = QAction(QIcon('connect24.png'), 'Connect', self)
        connectAction.setStatusTip('Connect to HW')
        connectAction.triggered.connect(self.establishCanConnection)

        enterRunningAction = QAction(QIcon('start24.png'), 'Start Running', self)
        enterRunningAction.setStatusTip('Start Running')
        enterRunningAction.triggered.connect(self.enterRunning)

        enterStandbyAction = QAction(QIcon('stop24.png'), 'Stop Running', self)
        enterStandbyAction.setStatusTip('Stop Running')
        enterStandbyAction.triggered.connect(self.enterStandby)

        self.statusBar()

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(exitAction)
        hwMenu = menubar.addMenu('&Hardware')
        hwMenu.addAction(connectAction)
        hwMenu.addAction(enterRunningAction)
        hwMenu.addAction(enterStandbyAction)

        toolbar = self.addToolBar('Exit')
        toolbar.addAction(exitAction)
        toolbar.addAction(connectAction)
        toolbar.addAction(enterRunningAction)
        toolbar.addAction(enterStandbyAction)

        self.setGeometry(300, 300, 400, 350) # x, y, width, height
        self.setWindowTitle('Demo Prog')
        self.show()

    def establishCanConnection(self):
        iDlg = QInputDialog(self)
        iDlg.setInputMode(QInputDialog.IntInput)
        idInt, ok = iDlg.getInt(self, 'CAN ID Selection', 'HW ID:')
        canID = '%s%d' % ('HW', idInt)
        if ok:
            self._workerThread.start()
            pass
        # this would be where the channel is established

    def enterRunning(self):
        self.sgnl_start_running.emit()
        # this would be where the command to start running is sent from

    def enterStandby(self):
        self.sgnl_stop_running.emit()
        # send the command to stop running


if __name__ == '__main__':

    app = QApplication(sys.argv)
    mainWindow = DemoMainWindow()
    sys.exit(app.exec_())

请注意,启动 _workerThread 的调用是在 establishCanConnection 方法中,但这应该不是问题,不是吗? 如果 establishCanConnection 为 运行,我使用 procmon 实用程序检查是否创建了更多线程,看起来有更多线程,但我发现很难将哪个线程(如果有的话)与 QThread 对象相关联.

除非确实需要,否则不要使用 BlockingQueuedConnection。如果你不知道你是否需要它,那你就不需要它。

跨线程信号在接收线程的事件循环中排队。如果该线程是阻塞的 运行 代码,它将无法处理任何事件。因此,如果您将带有 BlockingQueuedConnection 的信号发送到被阻塞的线程,您将陷入死锁。

您的示例使用了一个运行阻塞 while 循环的工作对象,因此它会受到上述死锁问题的影响。如果要向阻塞的线程发送信号,则需要安排阻塞代码定期允许线程处理其事件,如下所示:

    while not self._terminate:
        self.thread().sleep(1)
        QApplication.processEvents()

PS:

如果要检查worker是否运行在不同的线程中,可以打印QThread.currentThread()QThread.currentThreadId()的return值(这些函数是静态的,所以你不需要 QThread 的实例来调用它们。