Вопросы:
- Какова наилучшая практика для отслеживание протектора прогресс без блокировки GUI ( "Не реагировать" )?
- Как правило, какие наилучшие методы для как это применимо к GUI развитие?
Вопрос:
- У меня есть графический интерфейс PyQt для Windows.
- Он используется для обработки наборов HTML документы.
- Требуется от трех секунд до трех часов для обработки набора документы.
- Я хочу иметь возможность обрабатывать несколько наборов одновременно.
- Я не хочу, чтобы графический интерфейс блокировался.
- Я смотрю на модуль потоковой передачи для достижения этого.
- Я относительно новичок в потоковом режиме.
- GUI имеет один индикатор выполнения.
- Я хочу, чтобы он показывал ход выбранный поток.
- Отобразить результаты выбранного если он закончил.
- Я использую Python 2.5.
Моя идея:. При обновлении прогресса потоки выдают QtSignal, который запускает некоторые функции, которые обновляют индикатор выполнения. Также сигнализируйте, когда закончите обработку, чтобы результаты отображались.
#NOTE: this is example code for my idea, you do not have
# to read this to answer the question(s).
import threading
from PyQt4 import QtCore, QtGui
import re
import copy
class ProcessingThread(threading.Thread, QtCore.QObject):
__pyqtSignals__ = ( "progressUpdated(str)",
"resultsReady(str)")
def __init__(self, docs):
self.docs = docs
self.progress = 0 #int between 0 and 100
self.results = []
threading.Thread.__init__(self)
def getResults(self):
return copy.deepcopy(self.results)
def run(self):
num_docs = len(self.docs) - 1
for i, doc in enumerate(self.docs):
processed_doc = self.processDoc(doc)
self.results.append(processed_doc)
new_progress = int((float(i)/num_docs)*100)
#emit signal only if progress has changed
if self.progress != new_progress:
self.emit(QtCore.SIGNAL("progressUpdated(str)"), self.getName())
self.progress = new_progress
if self.progress == 100:
self.emit(QtCore.SIGNAL("resultsReady(str)"), self.getName())
def processDoc(self, doc):
''' this is tivial for shortness sake '''
return re.findall('<a [^>]*>.*?</a>', doc)
class GuiApp(QtGui.QMainWindow):
def __init__(self):
self.processing_threads = {} #{'thread_name': Thread(processing_thread)}
self.progress_object = {} #{'thread_name': int(thread_progress)}
self.results_object = {} #{'thread_name': []}
self.selected_thread = '' #'thread_name'
def processDocs(self, docs):
#create new thread
p_thread = ProcessingThread(docs)
thread_name = "example_thread_name"
p_thread.setName(thread_name)
p_thread.start()
#add thread to dict of threads
self.processing_threads[thread_name] = p_thread
#init progress_object for this thread
self.progress_object[thread_name] = p_thread.progress
#connect thread signals to GuiApp functions
QtCore.QObject.connect(p_thread, QtCore.SIGNAL('progressUpdated(str)'), self.updateProgressObject(thread_name))
QtCore.QObject.connect(p_thread, QtCore.SIGNAL('resultsReady(str)'), self.updateResultsObject(thread_name))
def updateProgressObject(self, thread_name):
#update progress_object for all threads
self.progress_object[thread_name] = self.processing_threads[thread_name].progress
#update progress bar for selected thread
if self.selected_thread == thread_name:
self.setProgressBar(self.progress_object[self.selected_thread])
def updateResultsObject(self, thread_name):
#update results_object for thread with results
self.results_object[thread_name] = self.processing_threads[thread_name].getResults()
#update results widget for selected thread
try:
self.setResultsWidget(self.results_object[thread_name])
except KeyError:
self.setResultsWidget(None)
Любые комментарии к этому подходу (например, недостатки, подводные камни, похвалы и т.д.) будут оценены.
Разрешение:
В итоге я использовал класс QThread и связанные с ним сигналы и слоты для связи между потоками. Это связано прежде всего с тем, что моя программа уже использует Qt/PyQt4 для объектов/виджетов GUI. Это решение также потребовало меньше изменений в моем существующем коде для реализации.
Вот ссылка на применимую статью Qt, в которой объясняется, как Qt обрабатывает потоки и сигналы, http://www.linuxjournal.com/article/9602. Выдержка ниже:
К счастью, Qt разрешает сигналов и слотов для подключения через потоки - пока потоки запускают собственные циклы событий. Это гораздо более чистый метод по сравнению с отправкой и получая события, поскольку он избегает все бухгалтерские и промежуточные QEvent-классы, которые становятся необходимо в любых нетривиальных выражение. Общение между потоки теперь становятся предметом подключение сигналов от одного потока к слоты в другом и мьютексирование и вопросы безопасности потоков при обмене данные между потоками обрабатываются Qt.
Почему необходимо запускать событие цикл внутри каждого потока, к которому вы хотите подключить сигналы? Причина имеет отношение к межпоточной коммуникационный механизм, используемый Qt при подключении сигналов от одного нить в слот другой нити. Когда такое соединение выполняется, оно называемое очередным соединением. Когда сигналы излучаются через очередь в очереди, вызывается слот в следующий раз, когда объект назначения выполняется цикл события. Если слот вместо этого вызывается непосредственно сигнал из другого потока, этот слот будет выполняться в том же контексте, что и вызывающий поток. Обычно это не то, что вы хотите (и особенно не что вы хотите, если используете соединение с базой данных, так как база данных соединение может использоваться только поток, который создал его). В очереди соединение правильно отправляет сигнал к объекту нити и вызывает свой слот в своем собственном контексте копирование в системе событий. Это именно то, что мы хотим межпоточная связь, в которой некоторые из потоков обрабатываются подключения к базе данных. Qt механизм сигнала/слота находится в корне реализация межпоточной описанная выше схема прохождения событий, но с гораздо более чистым и простой в использовании интерфейс.
ПРИМЕЧАНИЕ: у eliben также есть хороший ответ, и если бы я не использовал PyQt4, который обрабатывает безопасность потоков и мьютексинг, его решение было бы моим выбором.