В PyQt, каков наилучший способ обмена данными между главным окном и потоком
Я пишу свое первое приложение GUI с PyQt4, и я столкнулся с вопросом, который кажется очень простым, но я, похоже, не нахожу хорошего ответа:
Я использую поток для непрерывного выполнения повторяющейся задачи без блокировки главного окна. Потоку требуется некоторая информация из главного окна (например, текущее значение spinbox), которая также может изменяться во время выполнения потока. В этой ситуации, как правильно обмениваться такими данными между главным окном и нитью?
наивно, я мог бы придумать следующие возможности:
- передайте ссылку на окно хоста потоку и используйте это для получения текущего значения рассматриваемой переменной (см. пример ниже).
- сохраните копию переменной в потоке и синхронизируйте ее с главным окном, излучая сигналы при каждом ее изменении.
- использовать глобальные переменные.
все три варианта, скорее всего, будут работать для моего конкретного случая использования (хотя 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: я использую новый сигнал стиля и слоты