2012-06-29 3 views
11

Я пытаюсь сделать довольно обычное дело в моем приложении GUI PySide: я хочу делегировать некоторую задачу CPU-Intensive в фоновый поток, чтобы мой GUI оставался и может даже отображать индикатор прогресса по мере того, как вычисляется.PySide/PyQt - Запуск интенсивной работы процессора зависает всего приложения

Вот что я делаю (я использую 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(), программа будет вести себя так, как ожидалось.

Я точно не знаю, что происходит, но кажется, что приоритет второй нити настолько высок, что фактически предотвращает планирование графика графического интерфейса. Если второй поток переходит в состояние BLOCKED (как это происходит с sleep()), GUI имеет возможность запускать и обновлять интерфейс, как ожидалось. Я попытался изменить приоритет рабочего потока, но похоже, что это невозможно сделать в Linux.

Кроме того, я пытаюсь распечатать идентификаторы потоков, но я не уверен, правильно ли я делаю это. Если я, то сродство потоков кажется правильным.

Я также пробовал программу с PyQt, и поведение было точно таким же, следовательно, теги и заголовок. Если я могу заставить его работать с PyQt4 вместо PySide, я мог бы переключить все свое приложение на PyQt4

ответ

12

Возможно, это связано с тем, что рабочий поток поддерживает GIL Python. В некоторых реализациях Python может выполняться только один поток Python. GIL предотвращает выполнение другими потоками кода Python и освобождается во время вызовов функций, которым не нужен GIL.

Например, GIL освобождается во время фактического ввода-вывода, поскольку IO обрабатывается операционной системой, а не интерпретатором Python.

Решения:

  1. Судя по всему, вы можете использовать time.sleep(0) в вашем рабочем потоке, чтобы дать другим потокам (according to this SO question). Вам придется периодически называть time.sleep(0), а поток GUI будет работать только тогда, когда фоновый поток вызывает эту функцию.

  2. Если рабочий поток достаточно автономный, вы можете поместить его в совершенно отдельный процесс, а затем связаться, отправив травленные объекты по трубам. В процессе переднего плана создайте рабочий поток для выполнения ввода-вывода с фоновым процессом. Поскольку рабочий поток будет выполнять IO вместо операций с ЦП, он не будет удерживать GIL, и это даст вам полностью отзывчивый поток графического интерфейса.

  3. Некоторые реализации Python (JPython и IronPython) не имеют GIL.

Темы CPython только очень полезно для мультиплексирования операций ввода-вывода, а не для сдачи ресурсоемких задач в фоновом режиме. Для многих приложений потоки в реализации CPython принципиально нарушены, и это, вероятно, останется таким в обозримом будущем.

+0

Я попытался поместить 'time.sleep (0)' вместо комментария 'time.sleep (0.2)' в примере и, к сожалению, не исправляет проблему. Я заметил, что с такими значениями, как 'time.sleep (0.05)' кнопка ведет себя так, как ожидалось. В качестве обходного пути я мог настроить рабочего, чтобы сообщать о прогрессе только несколько раз в секунду и спать, чтобы позволить самому обновлению графического интерфейса. – user1491306

+1

Существуют обходные пути для потоков и графических интерфейсов (например, http://code.activestate.com/recipes/578154). –

+0

Я закончил так: я поставил 'sleep', где начинается вычисление, и каждый раз, когда индикатор прогресса обновляется, я вызываю' app.processEvents() ', чтобы заставить кнопку« Отменить »работать. – user1491306

0

в конце это работает для моей проблемы - так что код поможет кому-то другому.

import sys 
from PySide import QtCore, QtGui 
import time 

class qOB(QtCore.QObject): 

    send_data = QtCore.Signal(float, float) 

    def __init__(self, parent = None): 
     QtCore.QObject.__init__(self) 
     self.parent = None 
     self._emit_locked = 1 
     self._emit_mutex = QtCore.QMutex() 

    def get_emit_locked(self): 
     self._emit_mutex.lock() 
     value = self._emit_locked 
     self._emit_mutex.unlock() 
     return value 

    @QtCore.Slot(int) 
    def set_emit_locked(self, value): 
     self._emit_mutex.lock() 
     self._emit_locked = value 
     self._emit_mutex.unlock() 

    @QtCore.Slot() 
    def execute(self): 
     t2_z = 0 
     t1_z = 0 
     while True: 
      t = time.clock() 

      if self.get_emit_locked() == 1: # cleaner 
      #if self._emit_locked == 1: # seems a bit faster but less    responsive, t1 = 0.07, t2 = 150 
       self.set_emit_locked(0) 
       self.send_data.emit((t-t1_z)*1000, (t-t2_z)*1000) 
       t2_z = t 

      t1_z = t 

class window(QtGui.QMainWindow): 

    def __init__(self): 
     QtGui.QMainWindow.__init__(self) 

     self.l = QtGui.QLabel(self) 
     self.l.setText("eins") 

     self.l2 = QtGui.QLabel(self) 
     self.l2.setText("zwei") 

     self.l2.move(0, 20) 

     self.show() 

     self.q = qOB(self) 
     self.q.send_data.connect(self.setLabel) 

     self.t = QtCore.QThread() 
     self.t.started.connect(self.q.execute) 
     self.q.moveToThread(self.t) 

     self.t.start() 

    @QtCore.Slot(float, float) 
    def setLabel(self, inp1, inp2): 

     self.l.setText(str(inp1)) 
     self.l2.setText(str(inp2)) 

     self.q.set_emit_locked(1) 



if __name__ == '__main__': 

    app = QtGui.QApplication(sys.argv) 
    win = window() 
    sys.exit(app.exec_()) 

 Смежные вопросы

  • Нет связанных вопросов^_^