PyQt: подключение сигнала к слоту для запуска фоновой операции
У меня есть следующий код, который выполняет фоновую операцию (scan_value
) при обновлении индикатора выполнения в пользовательском интерфейсе (progress
). scan_value
перебирает некоторое значение в obj
, испускающий сигнал (value_changed
) каждый раз, когда значение изменяется. По причинам, которые здесь не актуальны, я должен обернуть это в объект (Scanner
) в другой теме. Сканер вызывается, когда кнопка scan
является clicked
, И тут возникает мой вопрос... следующий код работает нормально (т.е. индикатор выполнения обновляется вовремя).
# I am copying only the relevant code here.
def update_progress_bar(new, old):
fraction = (new - start) / (stop - start)
progress.setValue(fraction * 100)
obj.value_changed.connect(update_progress_bar)
class Scanner(QObject):
def scan(self):
scan_value(start, stop, step)
progress.setValue(100)
thread = QThread()
scanner = Scanner()
scanner.moveToThread(thread)
thread.start()
scan.clicked.connect(scanner.scan)
Но если я изменю последнюю часть на это:
thread = QThread()
scanner = Scanner()
scan.clicked.connect(scanner.scan) # This was at the end!
scanner.moveToThread(thread)
thread.start()
Индикатор выполнения обновляется только в конце (я предполагаю, что все работает в одном потоке). Должно ли это быть неактуально, если я подключу сигнал к слоту до или после перемещения объекта, принимающего объект, в поток.
2 ответа
Не должно иметь значения, установлено ли соединение до или после перемещения рабочего объекта в другой поток. Цитировать из документации Qt:
Qt:: AutoConnection - Если сигнал испускается из потока, отличного от принимающего объекта, сигнал ставится в очередь и ведет себя как Qt:: QueuedConnection. В противном случае слот вызывается напрямую, ведя себя как Qt:: DirectConnection. Тип соединения определяется при подаче сигнала. [выделение добавлено]
Итак, пока type
аргумент connect
установлен в QtCore.Qt.AutoConnection
(который используется по умолчанию), Qt должен гарантировать, что сигналы излучаются соответствующим образом.
Проблема с примером кода, скорее всего, связана со слотом, чем с сигналом. Метод python, к которому подключен сигнал, вероятно, должен быть помечен как слот Qt с использованием декоратора pyqtSlot:
from QtCore import pyqtSlot
class Scanner(QObject):
@pyqtSlot()
def scan(self):
scan_value(start, stop, step)
progress.setValue(100)
РЕДАКТИРОВАТЬ:
Следует уточнить, что только в сравнительно недавних версиях Qt тип соединения определяется при излучении сигнала. Это поведение было введено (наряду с некоторыми другими изменениями в поддержке многопоточности Qt) с версией 4.4.
Кроме того, возможно, стоит продолжить обсуждение проблемы, связанной с PyQt. В PyQt сигнал может быть подключен к слоту Qt, другому сигналу или любому вызываемому питону. В последнем случае прокси-объект создается внутри, который оборачивает вызываемый Python и предоставляет слот, который требуется для механизма Qt сигнал / слот.
Именно этот прокси-объект является причиной проблемы. Как только прокси создан, PyQt просто сделает это:
if (rx_qobj)
proxy->moveToThread(rx_qobj->thread());
что хорошо, если соединение установлено после того, как принимающий объект был перемещен в его поток; но если он сделан раньше, прокси останется в главном потоке.
С использованием @pyqtSlot
decorator вообще избегает этой проблемы, потому что он создает слот Qt более напрямую и вообще не использует прокси-объект.
Наконец, следует также отметить, что эта проблема в настоящее время не затрагивает PySide.
Моя проблема была решена путем перемещения соединения с местом инициализации рабочего потока, в моем случае, потому что я обращаюсь к объекту, который существует только после создания экземпляра моего класса Worker Object, который находится в другом потоке.
Просто подключите сигнал после self.createWorkerThread()
С уважением
Это связано с типами соединений Qt.
http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html
http://qt-project.org/doc/qt-4.8/qt.html
Если оба объекта находятся в одном потоке, создается стандартный тип соединения, что приводит к простому вызову функции. В этом случае трудоемкая операция происходит в потоке GUI и в интерфейсных блоках.
В случае, если тип соединения представляет собой соединение в стиле передачи сообщений, сигнал передается с использованием сообщения, которое обрабатывается в другом потоке. Поток GUI теперь свободен для обновления пользовательского интерфейса.
Когда вы не указываете тип соединения в функции соединения, тип определяется автоматически.