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 7

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

, что нормально, если соединение выполняется после перемещения принимающего объекта в его поток; но если оно выполняется до, прокси останется в основном потоке.

Использование декоратор @pyqtSlot полностью избегает этой проблемы, потому что он создает слот Qt более непосредственно и вообще не использует прокси-объект.

Наконец, следует также отметить, что этот вопрос в настоящее время не затрагивает PySide.

Это имеет отношение к типам соединений 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, тип определяется автоматически.