Я пытаюсь сделать довольно обычную вещь в своем приложении с графическим интерфейсом PySide: я хочу делегировать некоторую задачу, интенсивно использующую ЦП, фоновому потоку, чтобы мой графический интерфейс оставался отзывчивым и мог даже отображать индикатор выполнения по мере выполнения вычислений.
Вот что я делаю (использую PySide 1.1.1 на Python 2.7, Linux x86_64):
import sys
import time
from PySide.QtGui import QMainWindow, QPushButton, QApplication, QWidget
from PySide.QtCore import QThread, QObject, Signal, Slot
class Worker(QObject):
done_signal = Signal()
def __init__(self, parent = None):
QObject.__init__(self, parent)
@Slot()
def do_stuff(self):
print "[thread %x] computation started" % self.thread().currentThreadId()
for i in range(30):
# time.sleep(0.2)
x = 1000000
y = 100**x
print "[thread %x] computation ended" % self.thread().currentThreadId()
self.done_signal.emit()
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
self.work_thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.work_thread)
self.work_thread.started.connect(self.worker.do_stuff)
self.worker.done_signal.connect(self.work_done)
def initUI(self):
self.btn = QPushButton('Do stuff', self)
self.btn.resize(self.btn.sizeHint())
self.btn.move(50, 50)
self.btn.clicked.connect(self.execute_thread)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Test')
self.show()
def execute_thread(self):
self.btn.setEnabled(False)
self.btn.setText('Waiting...')
self.work_thread.start()
print "[main %x] started" % (self.thread().currentThreadId())
def work_done(self):
self.btn.setText('Do stuff')
self.btn.setEnabled(True)
self.work_thread.exit()
print "[main %x] ended" % (self.thread().currentThreadId())
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Приложение отображает одно окно с кнопкой. Когда кнопка нажата, я ожидаю, что она отключится во время выполнения вычисления. Затем кнопку следует снова активировать.
Вместо этого происходит то, что когда я нажимаю кнопку, все окно зависает, пока идет вычисление, а затем, когда оно завершено, я восстанавливаю контроль над приложением. Кнопка никогда не кажется отключенной. Забавная вещь, которую я заметил, заключается в том, что если я заменю интенсивные вычисления ЦП в do_stuff()
простым time.sleep(), программа будет вести себя так, как ожидалось.
Я точно не знаю, что происходит, но похоже, что приоритет второго потока настолько высок, что фактически предотвращает планирование потока GUI. Если второй поток переходит в состояние BLOCKED (как это происходит с sleep()
), GUI фактически имеет шанс запуститься и обновить интерфейс, как ожидалось. Я попытался изменить приоритет рабочего потока, но похоже, что это невозможно сделать в Linux.
Кроме того, я пытаюсь распечатать идентификаторы потоков, но не уверен, что делаю это правильно. Если да, то сходство потоков кажется правильным.
Я также попробовал программу с PyQt, и поведение точно такое же, отсюда и теги и заголовок. Если я смогу запустить его с PyQt4 вместо PySide, я смогу переключить все свое приложение на PyQt4.
time.sleep(0)
вместо комментарияtime.sleep(0.2)
в примере и, к сожалению, не решил проблему. Я заметил, что с такими значениями, какtime.sleep(0.05)
, кнопка ведет себя так, как ожидалось. В качестве обходного пути я мог бы настроить worker так, чтобы он сообщал о прогрессе только несколько раз в секунду и переходил в спящий режим, чтобы графический интерфейс обновлялся сам. 29.06.2012sleep
там, где начинается вычисление, и каждый раз, когда индикатор прогресса обновляется, я вызываюapp.processEvents()
, чтобы заставить работать кнопку «Прервать». 30.06.2012Threads in CPython are only really useful for multiplexing IO operations, not for putting CPU-intensive tasks in the background.
Нет Приложение Python на основе Qt, выполняющее задачу с интенсивным использованием ЦП в фоновом режиме, тривиально реализуемо. Почему? Потому что основной поток GUI тратит подавляющую часть своего временного интервала на чистом уровне C++ Qt, таким образом, обходя GIL Python. Графический интерфейс должен всегда реагировать в приложениях Python на основе Qt. Если это не так, вы случайно запускаете задачу с интенсивным использованием ЦП в основном потоке графического интерфейса. 31.03.2018