В PyQt, каков наилучший способ обмена данными между главным окном и потоком

Я пишу свое первое приложение GUI с PyQt4, и я столкнулся с вопросом, который кажется очень простым, но я, похоже, не нахожу хорошего ответа:

Я использую поток для непрерывного выполнения повторяющейся задачи без блокировки главного окна. Потоку требуется некоторая информация из главного окна (например, текущее значение spinbox), которая также может изменяться во время выполнения потока. В этой ситуации, как правильно обмениваться такими данными между главным окном и нитью?

наивно, я мог бы придумать следующие возможности:

  1. передайте ссылку на окно хоста потоку и используйте это для получения текущего значения рассматриваемой переменной (см. пример ниже).
  2. сохраните копию переменной в потоке и синхронизируйте ее с главным окном, излучая сигналы при каждом ее изменении.
  3. использовать глобальные переменные.

все три варианта, скорее всего, будут работать для моего конкретного случая использования (хотя 2 будет немного сложным), но у меня есть чувство, что должен быть лучший/более Питонический/более Qt-подобный способ.

вот минимальный рабочий пример, иллюстрирующий то, что я хочу сделать, в этом случае, используя вариант 1:

from PyQt4 import QtGui, QtCore
import time, sys

class MainWindow(QtGui.QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.layout = QtGui.QVBoxLayout(self)
        self.spinbox = QtGui.QSpinBox(self)
        self.spinbox.setValue(1)
        self.layout.addWidget(self.spinbox)
        self.output = QtGui.QLCDNumber(self)
        self.layout.addWidget(self.output)

        self.worker = Worker(self)
        self.connect(self.worker, QtCore.SIGNAL('beep'), self.update)
        self.worker.start()

    def update(self, number):
        self.output.display(number)


class Worker(QtCore.QThread):
    def __init__(self, host_window):
        super(Worker, self).__init__()
        self.host = host_window
        self.running = False

    def run(self):
        self.running = True
        i = 0
        while self.running:
            i += 1
            self.emit(QtCore.SIGNAL('beep'), i)
            sleep_time = self.host.spinbox.value()
            time.sleep(sleep_time)

    def stop(self):
        self.running = False


app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

PS: поскольку я совершенно неопытен в PyQt, маловероятно, что есть другие проблемы с кодом или вопрос неясен. В этом случае, пожалуйста, не стесняйтесь прокомментируйте или отредактируйте вопрос.

1 ответов


виджеты не являются потокобезопасными, см. потоки и QObjects:

хотя QObject является реентеративным, классы GUI, в частности QWidget и все его подклассы, не реентерабельны. Их можно только использовать от основной поток.

и смотрите больше определений здесь:Реентерабельность и потокобезопасность


вы должны использовать виджеты только в основном потоке и использовать сигнал и слоты для связи с другими нити.

Я не думаю, что глобальная переменная будет работать, но я честно не знаю почему.


Как использовать сигналы в этом примере:

#in main
self.worker = Worker(self.spinbox.value())
self.worker.beep.connect(self.update)
self.spinbox.valueChanged.connect(self.worker.update_value)

class Worker(QtCore.QThread):
    beep=QtCore.pyqtSignal(int)

    def __init__(self,sleep_time):
        super(Worker, self).__init__()
        self.running = False
        self.sleep_time=sleep_time

    def run(self):
        self.running = True
        i = 0
        while self.running:
            i += 1
            self.beep.emit(i)
            time.sleep(self.sleep_time)

    def stop(self):
        self.running = False

    def update_value(self,value):
        self.sleep_time=value

NB: я использую новый сигнал стиля и слоты