Ответ 1
Вам нужно разрешить обработку событий, пока цикл работает, чтобы приложение могло оставаться отзывчивым.
Еще важнее то, что для длительных задач вам необходимо предоставить пользователю возможность остановить цикл после его запуска.
Один простой способ сделать это - запустить цикл с помощью таймера, а затем периодически вызывать qApp.processEvents, пока цикл запущен.
Вот демо script, которое делает это:
import sys, time
from PyQt4 import QtGui, QtCore
class ProgressBar(QtGui.QWidget):
def __init__(self, parent=None, total=20):
super(ProgressBar, self).__init__(parent)
self.progressbar = QtGui.QProgressBar()
self.progressbar.setMinimum(1)
self.progressbar.setMaximum(total)
self.button = QtGui.QPushButton('Start')
self.button.clicked.connect(self.handleButton)
main_layout = QtGui.QGridLayout()
main_layout.addWidget(self.button, 0, 0)
main_layout.addWidget(self.progressbar, 0, 1)
self.setLayout(main_layout)
self.setWindowTitle('Progress')
self._active = False
def handleButton(self):
if not self._active:
self._active = True
self.button.setText('Stop')
if self.progressbar.value() == self.progressbar.maximum():
self.progressbar.reset()
QtCore.QTimer.singleShot(0, self.startLoop)
else:
self._active = False
def closeEvent(self, event):
self._active = False
def startLoop(self):
while True:
time.sleep(0.05)
value = self.progressbar.value() + 1
self.progressbar.setValue(value)
QtGui.qApp.processEvents()
if (not self._active or
value >= self.progressbar.maximum()):
break
self.button.setText('Start')
self._active = False
app = QtGui.QApplication(sys.argv)
bar = ProgressBar(total=101)
bar.show()
sys.exit(app.exec_())
UPDATE
Предполагая, что вы используете C-реализацию python (то есть CPython), решение этой проблемы полностью зависит от характера задачи (задач), которая должна запускаться одновременно с графическим интерфейсом. Более принципиально, он определяется CPython с Global Interpreter Lock (GIL).
Я не собираюсь пытаться объяснять CPython GIL: вместо этого я просто рекомендую посмотреть этот отличный PyCon video от Dave Beazley, и оставьте это на этом.
Как правило, при попытке запуска графического интерфейса одновременно с фоновым заданием первый вопрос, который задают, заключается в следующем: связана ли привязка IO или привязана к ЦП?
Если он связан с IO (например, для доступа к локальной файловой системе, загрузки из Интернета и т.д.), тогда решение обычно довольно просто, потому что CPython всегда выпускает операции GIL для операций ввода-вывода. Фоновая задача может быть просто выполнена асинхронно или выполнена рабочим потоком , и ничего особенного не нужно делать, чтобы поддерживать графический интерфейс.
Основные трудности возникают с задачами, связанными с ЦП, когда возникает второй вопрос: может ли задача разбиться на несколько небольших шагов?
Если это возможно, тогда решение состоит в том, чтобы периодически отправлять запросы в поток GUI для обработки текущего стека ожидающих событий. Демо-версия script выше является грубым примером этой техники. Как правило, задача будет выполняться в отдельном рабочем потоке, который будет генерировать сигнал gui-update, когда каждый шаг задачи будет завершен. (NB: важно убедиться, что рабочий поток никогда не пытается выполнить какие-либо операции, связанные с GUI).
Но если задачу нельзя разбить на небольшие шаги, то ни один из обычных решений типа нитей не будет работать. Графический интерфейс просто замерзнет, пока задача не будет выполнена, будут ли использоваться потоки или нет.
Для этого окончательного сценария единственным решением является использование отдельного процесса, а не отдельного потока, т.е. использование multiprocessing. Разумеется, это решение будет эффективно, только если целевая система имеет несколько ядер процессора. Если есть только одно центральное ядро для процессора, в основном ничего не может быть сделано для помощи (кроме переключения на другую реализацию Python или на другой язык вообще).