PyQt - 主要挂在 QThread

Posted

技术标签:

【中文标题】PyQt - 主要挂在 QThread【英文标题】:PyQt - Main hangs on QThread 【发布时间】:2018-06-20 20:54:58 【问题描述】:

我有一个连接到 CG 服务器 (CasparCG) 的 Python QT 应用程序。 QT 应用程序触发一个 QThread,它使用模块 pynput 监听热键 - 并向 CasparCG 发送一个命令,为每个按下的键播放不同的视频文件。

在主 GUI 中,我可以将视频文件分配给热键列表,并从子菜单项触发热键侦听线程。

self.actionStart_Hotkeys = QtWidgets.QAction(MainWindow)
self.menuCasparCG.addAction(self.actionStart_Hotkeys)    
self.actionStart_Hotkeys.triggered.connect(self.StartHotkeys)

主应用程序和 Ui_Window 代码很长,没有任何问题 - 它的功能应有尽有。当通过按键激活热键时,视频也会像我预期的那样播放,但是在播放几个视频文件后应用程序的主窗口会冻结 - 我不确定为什么主 GUI 在热键之后没有响应输入线程已启动。

到目前为止的代码看起来像这样..

from pynput import keyboard

class HotKeys(QThread):
    def __init__(self, parent):
        QThread.__init__(self, parent)
        self.COMBINATIONS = [
            keyboard.KeyCode(char='0'),
            keyboard.KeyCode(char='1'),
            keyboard.KeyCode(char='2'),
            keyboard.KeyCode(char='3'),  
            ]

        self.caspar = None
        self.current = set()
        self.Connect()
        self.Listen()

    def exit(self, i):
        if not self.caspar == None:
            self.caspar.close
        sys.exit(i)

    def Connect(self):
        try:
            self.caspar = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.caspar.connect(("127.0.0.1", 5250))
            print("Connected to caspar")

        except socket.error:
            print("CasparCG not running, or incorrect settings.xml")
            self.exit(0)

    def execute(self, k=None): # k is videofile 
        movie = bytes("PLAY 1-20  \r\n".format(k), 'utf8')
        self.caspar.send(movie)

    def on_press(self, key):
        if any([key in COMBO for COMBO in self.COMBINATIONS]):
             self.current.add(key)
            if any(all(k in self.current for k in COMBO) for COMBO in self.COMBINATIONS):
                self.execute(key)

    def on_release(self, key):
        if any([key in COMBO for COMBO in self.COMBINATIONS]):
            self.current.remove(key)

    def Listen(self):
        with keyboard.Listener(on_press=self.on_press, on_release=self.on_release) as listener:
            listener.join() 

我像这样在应用程序的 Main 类中触发这个 Hotkey QThread...

class Main(QMainWindow, Ui_MainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self) # from Ui_MainWindow class       

    def StartHotkeys(self):
        hotkey_thread = HotKeys(self)
        hotkey_thread.start()   

和这样的应用程序......

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv) 
    gui = Main()
    gui.show()
    sys.exit(app.exec_())

那么为什么 Main 会冻结?

【问题讨论】:

【参考方案1】:

QThread 不是线程,它是线程处理程序,如果你想在另一个线程中执行任务,你必须在 run() 方法中执行,该方法是唯一在另一个线程中执行的部分线。在您的情况下,Listen() 任务正在阻塞,您在构造函数中调用它,而 QThread 构造函数在 GUI 线程中运行,这就是您的 GUI 冻结的原因。解决办法是把Connect and Listen移到run()方法:

class HotKeys(QThread):
    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.COMBINATIONS = [
            keyboard.KeyCode(char='0'),
            keyboard.KeyCode(char='1'),
            keyboard.KeyCode(char='2'),
            keyboard.KeyCode(char='3'),  
            ]

        self.caspar = None
        self.current = set()

    def run(self):
        self.Connect()
        self.Listen()

    def exit(self, i):
        if self.caspar:
            self.caspar.close()
        sys.exit(i)

    def Connect(self):
        try:
            self.caspar = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.caspar.connect(("127.0.0.1", 10000))
            print("Connected to caspar")

        except socket.error:
            print("CasparCG not running, or incorrect settings.xml")
            self.exit(0)

    def execute(self, k=None): # k is videofile 
        if self.caspar:
            movie = bytes("PLAY 1-20  \r\n".format(k), 'utf8')
            self.caspar.send(movie)

    def on_press(self, key):
        if any([key in COMBO for COMBO in self.COMBINATIONS]):
            self.current.add(key)
            if any(all(k in self.current for k in COMBO) for COMBO in self.COMBINATIONS):
                self.execute(key)

    def on_release(self, key):
        if any([key in COMBO for COMBO in self.COMBINATIONS]):
            self.current.remove(key)

    def Listen(self):
        with keyboard.Listener(on_press=self.on_press, on_release=self.on_release) as listener:
            listener.join()

【讨论】:

非常清晰和简单的答案谢谢 - 我现在更了解处理程序类和实际运行的线程 - 我将它与运行在 init 上的代码混淆了。

以上是关于PyQt - 主要挂在 QThread的主要内容,如果未能解决你的问题,请参考以下文章

使用 C# 插件挂在 COM 应用程序中

将Docker主机数据挂在到容器中

自定义实现 PyQt5 下拉复选框 ComboCheckBox

PyQt5 思维导图之 简介

[工程-学习笔记] Pyqt5常用组件

[工程-学习笔记] Pyqt5常用组件