PyQt - Main зависает на QThread

У меня есть приложение Python QT, которое подключается к серверу CG (CasparCG). Приложение QT запускает QThread, который прослушивает горячие клавиши с помощью модуля pynput и отправляет команду CasparCG для воспроизведения разных видеофайлов для каждой из нажатых клавиш.

В основном графическом интерфейсе я могу назначить видеофайлы списку горячих клавиш и запустить поток прослушивания горячих клавиш из пункта подменю.

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

Код основного приложения и Ui_Window очень длинный и не имеет проблем - он работает так, как должен. Видео также воспроизводится, как я и ожидал, когда горячие клавиши активируются нажатием клавиш, но главное окно приложения зависает после воспроизведения нескольких видеофайлов - и я не уверен, почему основной графический интерфейс не отвечает на ввод после горячей клавиши поток запущен.

Код до сих пор выглядит так.

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() 

Я запускаю эту горячую клавишу 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 зависает?


person Xeberdee    schedule 20.06.2018    source источник


Ответы (1)


QThread - это не поток, это обработчик потока, если вы хотите выполнить задачу в другом потоке, вы должны сделать это в методе run(), этот метод является единственной частью, которая выполняется в другом потоке. В вашем случае задача Listen() блокируется, и вы вызываете ее в конструкторе, а конструктор QThread запускается в потоке графического интерфейса, поэтому ваш графический интерфейс зависает. Решение состоит в том, чтобы переместить 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()
person eyllanesc    schedule 20.06.2018
comment
Очень ясный и простой ответ, спасибо - теперь я больше понимаю класс обработчика и фактически работающий поток - я путал его с кодом, который запускается при инициализации. - person Xeberdee; 21.06.2018