Как настроить сигналы и слоты в PyQt с QThreads в обоих направлениях? - PullRequest
0 голосов
/ 25 октября 2018

Это дополнительный вопрос, основанный на ответах эхуморо здесь и здесь .


Я подумал, что когда слот правильно определенс pyqtSlot и назначенным QThread (например, с moveToThread()), он будет выполняться в этом QThread, а не в вызывающем.Кроме того, также необходимо установить соединение с Qt.QueuedConnection или Qt.AutoConnection.

Я написал код для проверки этого.Моя цель состоит в том, чтобы добиться чего-то довольно простого, такого как: enter image description here
Gui с кнопкой, которая запускает некоторую трудоемкую работу и возвращает результат с отображением обратно в GUI.

from PyQt5.Qt import *

class MainWindow(QMainWindow):
    change_text = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.button = QPushButton('Push me!', self)
        self.setCentralWidget(self.button)

        print('main running in:', QThread.currentThread())
        thread = Thread(change_text, self)
        thread.start()
        self.button.clicked.connect( thread.do_something_slow, Qt.QueuedConnection)
        self.change_text.connect(self.display_changes, Qt.QueuedConnection)

    @pyqtSlot(str)
    def display_changes( self, text ):
        self.button.setText(text)

class Thread(QThread):
    def __init__(self, signal_to_emit, parent):
        super().__init__(parent)
        self.signal_to_emit = signal_to_emit
        #self.moveToThread(self) #doesn't help

    @pyqtSlot()
    def do_something_slow( self ):
        print('Slot doing stuff in:', QThread.currentThread())
        import time
        time.sleep(5)
        self.signal_to_emit.emit('I did something')

if __name__ == '__main__':
    app = QApplication([])
    main = MainWindow()
    main.show()
    app.exec()

Но ... графический интерфейс блокируется, и слот вызывается в главном потоке.
Чего мне не хватает?Надо быть чем-то маленьким (надеюсь).

1 Ответ

0 голосов
/ 25 октября 2018

Проблема в том, что вы путаете, что QThread - это Qt-поток , то есть новый поток, созданный Qt, но нет, QThread - это класс, который обрабатывает нативные потоки, только метод run() выполняется в другом потоке, другие методы живут в потоке, в котором находится QThread, то есть QObject,

В каком потокеa QObject live?

Поток, в котором живет QObject, является потоком родителя, если у него нет родителя, он будет потоком, в котором он был создан.С другой стороны, QObject может перейти в другой поток, используя moveToThread(), и все его дочерние элементы также будут перемещены.Только moveToThread() может использоваться, если QObject не имеет родителя, в противном случае произойдет сбой.


Методология использования QThread заключается в создании класса, который наследуется от QThread, и переопределенииметод run и вызов start(), чтобы запустить его run(), в методе run() тяжелая задача будет выполнена, но в вашем случае нет. Эту форму можно использовать, поскольку задача не будет выполняться непрерывно.Лучшим вариантом, чем тяжелая задача, является часть QObject, и QObject перемещает ее в другой поток.

class MainWindow(QMainWindow):
    change_text = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.button = QPushButton('Push me!', self)
        self.setCentralWidget(self.button)
        print('main running in:', QThread.currentThread())
        # A Worker without a parent is created 
        # so that it can be moved to another thread.
        self.worker = Worker(self.change_text)
        thread = QThread(self) 
        self.worker.moveToThread(thread)
        thread.start()
        # All methods of self.worker are now executed in another thread.
        self.button.clicked.connect(self.worker.do_something_slow)
        self.change_text.connect(self.display_changes)

    @pyqtSlot(str)
    def display_changes( self, text ):
        self.button.setText(text)


class Worker(QObject):
    def __init__(self, signal_to_emit, parent=None):
        super().__init__(parent)
        self.signal_to_emit = signal_to_emit

    @pyqtSlot()
    def do_something_slow( self ):
        print('Slot doing stuff in:', QThread.currentThread())
        import time
        time.sleep(5)
        self.signal_to_emit.emit('I did something')

С другой стороны, указывать тип соединения не обязательно, посколькупо умолчанию это Qt::AutoConnection, этот тип соединения решает во время выполнения, если вы используете Qt::DirectConnection, если приемник живет в том же проводе, откуда исходил сигнал, в противном случае используется Qt::QueuedConnection.Как вы понимаете, у Worker нет родителя, поэтому для жизненного цикла, равного классу, он должен быть его атрибутом, поскольку в противном случае это будет локальная переменная, которая будет исключена.С другой стороны, обратите внимание, что QThread получает родительский объект, а не MainWindow, поэтому QThread будет жить в потоке GUI, но обрабатывает вторичный поток.


С концепцией Worker лучшечто сигнал change_text больше не принадлежит графическому интерфейсу, а рабочему лучше отсоединяет объекты.

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.button = QPushButton('Push me!', self)
        self.setCentralWidget(self.button)

        print('main running in:', QThread.currentThread())
        self.worker = Worker()
        thread = QThread(self)
        self.worker.moveToThread(thread)
        thread.start()
        self.button.clicked.connect(self.worker.do_something_slow)
        self.worker.change_text.connect(self.display_changes)

    @pyqtSlot(str)
    def display_changes( self, text ):
        self.button.setText(text)

class Worker(QObject):
    change_text = pyqtSignal(str)

    @pyqtSlot()
    def do_something_slow( self ):
        print('Slot doing stuff in:', QThread.currentThread())
        import time
        time.sleep(5)
        self.change_text.emit('I did something')
...