Подтвердить что ты не робот

Как отслеживать прогресс потоков в Python без замораживания графического интерфейса PyQt?

Вопросы:

  • Какова наилучшая практика для отслеживание протектора прогресс без блокировки 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, который обрабатывает безопасность потоков и мьютексинг, его решение было бы моим выбором.

4b9b3361

Ответ 1

Если вы хотите использовать сигналы для указания прогресса на основной поток, то вы действительно должны использовать класс PyQt QThread вместо класса Thread из модуля потоковой передачи Python.

Простой пример, который использует QThread, сигналы и слоты, можно найти в PyQt Wiki:

https://wiki.python.org/moin/PyQt/Threading,_Signals_and_Slots

Ответ 2

Одиночные очереди python не будут работать, потому что вам нужно заблокировать очередь get(), которая расширяет ваш пользовательский интерфейс.

Qt по существу реализует систему массового обслуживания внутри для передачи по потоку. Попробуйте этот вызов из любого потока, чтобы отправить звонок в слот.

QtCore.QMetaObject.invokeMethod()

Это неудобно и плохо документировано, но оно должно делать то, что вы хотите, даже из потока, отличного от Qt.

Вы также можете использовать механизмы событий для этого. См. QApplication (или QCoreApplication) для метода с именем "post".

Изменить: здесь более полный пример...

Я создал свой собственный класс на основе QWidget. Он имеет слот, который принимает строку; Я определяю его так:

@QtCore.pyqtSlot(str)
def add_text(self, text):
   ...

Позже я создаю экземпляр этого виджета в основном потоке графического интерфейса. Из основной нити GUI или любой другой нити (стук по дереву) я могу позвонить:

QtCore.QMetaObject.invokeMethod(mywidget, "add_text", QtCore.Q_ARG(str,"hello world"))

Неуклюжий, но он попадает туда.

Dan.

Ответ 3

Я рекомендую вам использовать Очередь вместо сигнализации. Лично я считаю, что это гораздо более надежный и понятный способ программирования, потому что он более синхронный.

Темы должны получать "задания" из очереди и возвращать результаты в другую очередь. Тем не менее, третью очередь можно использовать потоками для уведомлений и сообщений, таких как ошибки и "отчеты о проделанной работе". Как только вы структурируете свой код таким образом, он становится намного проще управлять.

Таким образом, одна "очередь заданий" и "очередь результатов" также могут использоваться группой рабочих потоков, она направляет всю информацию из потоков в основной поток графического интерфейса.

Ответ 4

Если ваш метод "processDoc" не изменяет никаких других данных (просто ищет некоторые данные и возвращает их и не меняет переменные или свойства родительского класса), вы можете использовать макросы Py_BEGIN_ALLOW_THREADS и Py_END_ALLOW_THREADS (подробнее см. здесь). Таким образом, документ будет обрабатываться в потоке, который не будет блокировать интерпретатор, и пользовательский интерфейс будет обновлен.

Ответ 5

У вас всегда будет эта проблема в Python. Google GIL "глобальная блокировка интерпретатора" для получения дополнительной информации. Существует два рекомендуемых способа устранения проблемы, которую вы испытываете: используйте Twisted или используйте модуль, аналогичный multiprocessing, введенный в 2.5.

Twisted потребует, чтобы вы изучали асинхронные методы программирования, которые могут сбивать с толку вначале, но будут полезны, если вам когда-либо понадобится писать приложения с высокой пропускной способностью и будет более выгодным для вас в долгосрочной перспективе.

Модуль многопроцессорности будет вибрировать новый процесс и использует IPC, чтобы заставить его вести себя так, как если бы у вас была истинная потоковая передача. Единственным недостатком является то, что вам понадобится установить python 2.5, который является довольно новым и будет включен в большинство дистрибутивов Linux или OSX по умолчанию.