概要 PyQt5似乎没有创建与QThread对象相对应的新线程,或者我没有正确建立Slot / Signal链接。请帮我隔离我的问题。
我是Python的一个相对随意的用户,但我被要求为另一个团队创建一个实用程序,该团队将一些Python库(它们自己包装C ++)包装在GUI中。因为这个实用程序是针对另一个团队的,所以我不能更改编译器的版本等,或者至少,不能没有提供合理的理由。 该实用程序旨在提供一个接口,用于调试我的同事正在开发的某些硬件。
在检查选项后,我决定使用Qt和PyQt绑定。我遵循的步骤是:
我编写的代码产生了UI,并且在某种意义上是响应的,如果我在从QAction对象调用的方法中放置断点,那么这些断点就会被适当地触发。我的问题是我创建的Worker对象似乎没有移动到单独的线程(尽管调用了moveToThread),因为如果我连接的类型为BlockingQueuedConnection而不是QueuedConnection,那么我会遇到死锁。我在工作者类型的插槽上放置的断点永远不会被触发。
这是代码::
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对象相关联。
答案 0 :(得分:0)
除非您真的需要,否则不要使用BlockingQueuedConnection
。如果您不知道是否需要它,那么您不需要它。
跨线程信号在接收线程的事件循环中排队。如果该线程正在运行阻塞的代码,则它将无法处理任何事件。因此,如果您将BlockingQueuedConnection
的信号发送到被阻止的线程,您将陷入僵局。
您的示例使用运行阻塞while循环的worker对象,因此它受上面列出的死锁问题的影响。如果要将信号发送到被阻止的线程,则需要安排阻塞代码以定期允许线程处理其事件,如下所示:
while not self._terminate:
self.thread().sleep(1)
QApplication.processEvents()
PS:
如果要检查worker是否在另一个线程中运行,可以打印返回值QThread.currentThread()
或QThread.currentThreadId()
(这些函数是静态的,因此您不需要实例QThread
来称呼他们。)