线程Python中的Qt信号/插槽

时间:2013-12-31 03:19:17

标签: python multithreading qt pyqt signals

我在使用PyQt4插槽/信号时遇到了麻烦。

我正在使用PyLIRC,我正在听遥控器上的按键按下。这部分我已经在Qt以外的地方工作了。当从按钮监听线程发出信号并试图调用主线程中的插槽时,我的问题出现了。

我的按钮监听器是一个像这样初始化的QObject:

buttonPressed = pyqtSignal(int)

def __init__(self):
    super(ButtonEvent, self).__init__()
    self.buttonPressed.connect(self.onButtonPressed)

def run(self):
    print 'running'
    while(self._isListening):
        s = pylirc.nextcode()
        if (s):
            print 'emitting'
            self.buttonPressed.emit(int(s[0]))

onButtonPressed插槽位于按钮侦听器的内部,用于测试目的。

要将按钮侦听器移动到另一个线程来完成工作,我使用以下命令:

event = ButtonEvent()
eventThread = QThread()
event.moveToThread(eventThread)
eventThread.started.connect(event.run)

然后在主线程中,我的VideoTableController类包含主线程中不会被调用的槽。 __init__内部我有这个:

class VideoTableController(QObject):
    def __init__(self, buttonEvent):
        buttonEvent.buttonPressed.connect(self.onButtonPressed)

在这种情况下,onButtonPressed为:

@pyqtSlot(int)
def onButtonPressed(self, bid):
    print 'handling button press'
    if bid not in listenButtons: return
    { ButtonEnum.KEY_LEFT : self.handleBack,
    #...

因此,当我启动事件线程时,它会开始正确监听。当我按下遥控器上的按钮时,onButtonPressed类内部的ButtonEvent插槽被正确调用,但不会调用驻留在主线程中的VideoTableController内的插槽。在将插槽连接到信号后我开始了我的监听线程,我测试了相反的方式,但无济于事。

我环顾四周,但我找不到任何东西。我在阅读You're doing it wrong后转而使用QObject。非常感谢任何帮助。如果您还有其他需要,请告诉我。

编辑:感谢您的回复!以下是您的大量代码:

ButtonEvent(这个类使用单例模式,原谅糟糕的编码,因为我对Python的这个领域也有些新意见):

import pylirc
from PyQt4.QtCore import QObject, pyqtSignal, QThread, pyqtSlot
from PyQt4 import QtCore

class ButtonEvent(QObject):
    """
    A class used for firing button events
    """

    _instance = None
    _blocking = 0
    _isListening = False

    buttonPressed = pyqtSignal(int)

    def __new__(cls, configFileName="~/.lircrc", blocking=0, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(ButtonEvent, cls).__new__(cls, args, kwargs)
            cls._blocking = blocking
            if not pylirc.init("irexec", configFileName, blocking):
                raise RuntimeError("Problem initilizing PyLIRC")
            cls._isListening = True

        return cls._instance

    def __init__(self):
        """
        Creates an instance of the ButtonEvent class
        """
        super(ButtonEvent, self).__init__()
        self.buttonPressed.connect(self.button)
    ### init

    def run(self):
        print 'running'
        while(self._isListening):
            s = pylirc.nextcode()
            if (s):
                print 'emitting'
                self.buttonPressed.emit(int(s[0]))

    def stopListening(self):
        print 'stopping'
        self._isListening = False

    @pyqtSlot(int)
    def button(self, bid):
        print 'Got ' + str(bid)


def setupAndConnectButtonEvent(configFileName="~/.lircrc", blocking=0):
    """
    Initializes the ButtonEvent and puts it on a QThread.
    Returns the QThread it is running on.
    Does not start the thread
    """
    event = ButtonEvent().__new__(ButtonEvent, configFileName, blocking)
    eventThread = QThread()
    event.moveToThread(eventThread)
    eventThread.started.connect(event.run)
    return eventThread

这是VideoTableController:

from ControllerBase import ControllerBase
from ButtonEnum import ButtonEnum
from ButtonEvent import ButtonEvent
from PyQt4.QtCore import pyqtSlot
from PyQt4 import QtCore

class VideoTableController(ControllerBase):

    listenButtons = [ ButtonEnum.KEY_LEFT,   
                      ButtonEnum.KEY_UP,     
                      ButtonEnum.KEY_OK,     
                      ButtonEnum.KEY_RIGHT,  
                      ButtonEnum.KEY_DOWN,   
                      ButtonEnum.KEY_BACK ]

    def __init__(self, model, view, parent=None):
        super(VideoTableController, self).__init__(model, view, parent)
        self._currentRow = 0
        buttonEvent = ButtonEvent()
        buttonEvent.buttonPressed.connect(self.onButtonPressed)
        self.selectRow(self._currentRow)

    @pyqtSlot(int)
    def onButtonPressed(self, bid):
        print 'handling button press'
        if bid not in listenButtons: return
        { ButtonEnum.KEY_LEFT : self.handleBack,
          ButtonEnum.KEY_UP : self.handleUp,
          ButtonEnum.KEY_OK : self.handleOk,
          ButtonEnum.KEY_RIGHT : self.handleRight,
          ButtonEnum.KEY_DOWN : self.handleDown,
          ButtonEnum.KEY_BACK : self.handleBack,
        }.get(bid, None)()

这是我的启动脚本:

import sys
from PyQt4 import QtCore, QtGui
from ui_main import Ui_MainWindow
from VideoTableModel import VideoTableModel
from VideoTableController import VideoTableController
from ButtonEvent import *

class Main(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.buttonEvent = ButtonEvent()
        self.bEventThread = setupAndConnectButtonEvent()
        model = VideoTableModel("/home/user/Videos")
        self.ui.videoView.setModel(model)
        controller = VideoTableController(model, self.ui.videoView)
        self.bEventThread.start()

    def closeEvent(self, event):
        self.buttonEvent.stopListening()
        self.bEventThread.quit()
        event.accept()


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    buttonEvent = ButtonEvent()
    myapp = Main()
    myapp.show()
    sys.exit(app.exec_())    

3 个答案:

答案 0 :(得分:1)

事实证明我只是犯了一个愚蠢的Python错误。信号正确发出,事件循环在所有线程中正常运行。我的问题是在我的Main.__init__函数中我创建了一个VideoTableController对象,但我没有在Main中保留副本,所以我的controller没有保留,这意味着插槽也离开了。将其更改为

self.controller = VideoTableController(model, self.ui.videoView)

一切都待在那里,插槽被正确调用。

故事的道德:它并不总是滥用图书馆,可能是对该语言的误用。

答案 1 :(得分:0)

似乎最快的解决方法是在此处更改您的ButtonEvent代码:

...
def run(self):
    print 'running'
    while(self._isListening):
        s = pylirc.nextcode()
        if (s):
            print 'emitting'
            self.buttonPressed.emit(int(s[0]))
...

到此:

@pyqtSlot()
def run(self):
    print 'running'
    while(self._isListening):
        s = pylirc.nextcode()
        if (s):
            print 'emitting'
            self.buttonPressed.emit(int(s[0]))

对此问题的简短解释是PyQt在内部使用代理,这样您就可以确保避免这种情况。毕竟,您的方法应该是基于connect语句的插槽。

对......现在,我鼓励您考虑一下当前的软件设计。您似乎在专用线程中使用类来处理Qt按钮事件。这可能是个好主意,我不确定,但我至少没有见过这个。

我认为将来可以通过更好的方法完全摆脱该类,从按钮信号直接连接到处理程序插槽。但是,这不是你的专用线程中的run“槽”,而是一个规范的处理程序。

引入更多复杂性(尤其是在多线程应用程序中)并不是一个好的设计实践。希望这会有所帮助。

答案 2 :(得分:-1)

我实际上没有对此进行测试(因为我无法访问您编译的UI文件),但我确信我是对的。

你的ButtonEvent的run方法(应该在一个线程中运行)很可能在mainthread中运行(你可以通过导入python threading模块并添加行print threading.current_thread().name来测试它要解决此问题,请使用@pyqtSlot()

修饰run方法

如果仍然无法解决问题,请将上述print语句添加到各个位置,直到找到主线程中不应运行的内容为止。下面列出的SO答案可能包含解决问题的答案。

有关详细信息,请参阅此答案:https://stackoverflow.com/a/20818401/1994235