PyQt: подключение сигнала к слоту для запуска фоновой операции
У меня есть следующий код, который выполняет фоновую операцию (scan_value
) при обновлении индикатора выполнения в пользовательском интерфейсе (progress
). scan_value
перебирает некоторое значение в obj
, испуская сигнал (value_changed
) каждый раз, когда значение изменяется. По причинам, которые здесь не уместны, я должен обернуть это в Объект (Scanner
) в другой поток. Сканер вызывается, когда кнопка a 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 docs:
Таким образом, пока аргумент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)
EDIT:
Следует уточнить, что только в относительно недавних версиях Qt тип соединения определяется при испускании сигнала. Это поведение было введено (наряду с несколькими другими изменениями в многопоточной поддержке Qt) с версией 4.4.
Кроме того, возможно, стоит подробнее остановиться на вопросе, касающемся PyQt. В PyQt сигнал может быть подключен к слоту Qt, другому сигналу или любому вызываемому python. Для в последнем случае внутри создается прокси-объект, который обертывает вызываемый python и предоставляет слот, необходимый для механизма Qt signal/slot.
Именно этот прокси-объект является причиной проблемы. Как только прокси будет создан, PyQt просто сделает это:if (rx_qobj) proxy->moveToThread(rx_qobj->thread());
, что нормально, если соединение выполняется после перемещения принимающего объекта в его поток; но если оно выполняется до, прокси останется в основном потоке.
Использование декоратор
Наконец, следует также отметить, что этот вопрос в настоящее время не затрагивает PySide.@pyqtSlot
полностью избегает этой проблемы, потому что он создает слот Qt более непосредственно и вообще не использует прокси-объект.
Это имеет отношение к типам соединений Qt.
Http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html#connect
Http://qt-project.org/doc/qt-4.8/qt.html#ConnectionType-enum
В случае, если оба объекта находятся в одном потоке, создается стандартный тип соединения, что приводит к простому вызову функции. В этом случае трудоемкая операция происходит в графическом потоке и интерфейсных блоках.
В случае, если тип соединения сообщение, передающее стиль соединения, сигнал испускается с помощью сообщения, которое обрабатывается в другом потоке. В GUI-потоке, чтобы обновить пользовательский интерфейс.
Если тип соединения не указан в функции connect, тип определяется автоматически.