Pergunta

Perguntas:

  1. O que é a melhor prática para manter o controle de uma banda de rodagem de progredir sem bloquear a GUI ( "Não está respondendo")?
  2. Em geral, quais são as melhores práticas para enfiando como se aplica a GUI desenvolvimento?

Pergunta fundo:

  • Eu tenho um PyQt GUI para Windows.
  • É usado para conjuntos de processos de HTML documentos.
  • Ele leva de três segundos de três horas a processar um conjunto de documentos.
  • Eu quero ser capaz de processo vários conjuntos ao mesmo tempo.
  • Não quero a GUI para bloqueio.
  • Eu estou olhando para o módulo de threading para alcançar este objectivo.
  • Eu sou relativamente novo para threading.
  • A GUI tem uma barra de progresso.
  • Eu quero isso para mostrar o progresso da o segmento selecionado.
  • Mostrar resultados do selecionado thread se ele foi concluído.
  • Eu estou usando Python 2.5.

My Idea: Tenha os tópicos emitir um QtSignal quando o progresso é atualizado que desencadeia alguma função que atualiza a barra de progresso. Também sinalizar quando terminou de processar por isso os resultados podem ser exibidos.

#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)

qualquer comentário sobre esta abordagem (por exemplo inconvenientes, armadilhas, virtudes, etc.) vai ser apreciado.

Solução:

Acabei usando a classe QThread e sinais associados e ranhuras para comunicação entre threads. Isso ocorre principalmente porque o meu programa já usa Qt / PyQt4 para os objetos GUI / widgets. Esta solução também precisaram de menos alterações no meu código existente para implementar.

Aqui está um link para um artigo Qt aplicável que explica como Qt lida com threads e sinais, http: // www.linuxjournal.com/article/9602 . Trecho abaixo:

Felizmente, Qt permite sinais e ranhuras para ser ligado entre segmentos, contanto que os fios estão executando seus próprios laço de eventos. Este é um método mais limpo muito do comunicação em comparação com o envio e receber eventos, porque evita toda a contabilidade e intermediário Classes derivadas de QEvent que se tornam necessário em qualquer nontrivial inscrição. comunicação entre tópicos agora se torna uma questão de ligar os sinais a partir de um fio de as ranhuras na outra, e a mutexing e questões thread-segurança de troca dados entre segmentos são tratadas por Qt.

Por que é necessário para executar um evento circuito dentro de cada segmento para o qual você deseja se conectar sinais? O motivo tem a ver com a inter-thread mecanismo de comunicação utilizado pelo Qt ao ligar os sinais de um linha para o slot de outro segmento. Quando tal conexão é feita, é referido como uma ligação na fila. Quando os sinais são emitidos através de um conexão na fila, o slot é invocado da próxima vez que o objeto de destino é ciclo de eventos é executado. Se o slot em vez tinha sido invocado directamente por um sinal a partir de uma outra linha, que ranhura será executado no mesmo contexto o segmento de chamada. Normalmente, esta é não o que você quer (e, especialmente, não o que você quer se você estiver usando um conexão da base de dados, como a base de dados conexão pode ser utilizado apenas pelo segmento que criou). a fila de espera conexão despacha corretamente o sinalizar para o objecto de discussão e invoca seu slot em seu próprio contexto, piggy-backing no sistema evento. Este é precisamente o que queremos para comunicação inter-thread em que alguns dos tópicos estão lidando ligações de base de dados. o Qt mecanismo de sinal / ranhura encontra-se numa raiz implementação da inter-thread esquema de passagem de evento descrito acima, mas com uma muito mais limpo e interface mais fácil de usar.

NOTA: eliben também tem uma boa resposta, e se eu não estivesse usando PyQt4, que alças thread-segurança e mutexing, sua solução teria sido a minha escolha.

Foi útil?

Solução

Se você quiser usar sinais para indicar o progresso para o segmento principal, então você realmente deve estar usando classe QThread de PyQt em vez da classe Thread do módulo de threading do Python.

Um exemplo simples que usa QThread, sinais e slots pode ser encontrado na PyQt Wiki:

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

Outras dicas

Native filas Python não trabalho vai porque você tem que bloquear na fila get (), que bungs até sua UI.

Qt essencialmente implementa um sistema de filas no interior para a comunicação fio cruz. Tente esta chamada de qualquer segmento para enviar uma chamada para um slot.

QtCore.QMetaObject.invokeMethod ()

É desajeitado e está mal documentada, mas deve fazer o que quiser, mesmo a partir de um Qt non-thread.

Você também pode usar máquinas de eventos para este. Veja QApplication (ou QCoreApplication) para um método chamado algo como "post".

Edit: Aqui está um exemplo mais completo ...

Eu criei minha própria classe com base em QWidget. Ele tem um slot que aceita uma string; Eu defini-lo como este:

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

Mais tarde, eu criar uma instância desse widget na thread principal GUI. A partir do thread principal GUI ou qualquer outro thread (bata na madeira) eu posso chamar:

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

Clunky, mas você fica lá.

Dan.

Eu recomendo que você use fila em vez de sinalização. Pessoalmente acho que é uma forma muito mais robusta e compreensível de programação, porque é mais síncrona.

Threads deve obter "empregos" de uma fila, e colocar de volta os resultados em outra fila. Ainda uma terceira fila pode ser usado por os fios para as notificações e mensagens, como erros e "relatórios de progresso". Uma vez que você estruturar seu código desta forma, torna-se muito mais simples de gerenciar.

Desta forma, um único "trabalho da fila" e "resultar Queue" também pode ser usado por um grupo de segmentos de trabalho, ele encaminha todas as informações a partir dos tópicos para o segmento principal GUI.

Se o seu método "processDoc" não muda quaisquer outros dados (só olha para alguns dados e devolvê-lo e não mudam variáveis ??ou propriedades de classe pai) você pode usar Py_BEGIN_ALLOW_THREADS e Py_END_ALLOW_THREADS macroses ( veja aqui para detalhes ) nele. Assim, o documento será processado de discussão que não vai bloquear o intérprete e UI será atualizado.

Você está sempre vai ter esse problema em Python. Google GIL "bloqueio interpretor global" para mais fundo. Há duas maneiras geralmente recomendadas para contornar o problema que você está enfrentando: uso torcida , ou usar um módulo semelhante ao multiprocessamento módulo introduzido em 2.5.

torcida vai exigir que você aprenda as técnicas de programação assíncrona que pode ser confuso no início, mas será útil se você precisar escrever aplicações de rede de alto rendimento e será mais benéfico para você no longo prazo.

O módulo de multiprocessamento irá desembolsar um novo processo e usa IPC para torná-lo comportar-se como se você tivesse verdadeira threading. A única desvantagem é que você precisaria de python 2.5 instalado, que é relativamente novo e inst' incluído na maioria das distribuições Linux ou OSX por padrão.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top