Pergunta

Como alguém novo para o desenvolvimento de GUI em Python (com pyGTK), eu só comecei a aprender sobre threading. Para testar minhas habilidades, eu escrevi uma interface GTK simples e pequeno com um botão start / stop. O objetivo é que quando é clicado, um segmento começa que rapidamente incrementa um número na caixa de texto, mantendo o GUI responsivo.

Eu tenho o GUI funcionando muito bem, mas estou tendo problemas com o threading. É provavelmente um problema simples, mas minha mente está sobre frito para o dia. Abaixo eu ter colado primeiro o trackback a partir do interpretador Python, seguido do código. Você pode ir para http://drop.io/pxgr5id para baixá-lo. Estou usando bzr para controle de revisão, por isso, se você quiser fazer uma modificação e re-soltá-lo, por favor confirmar as alterações. Eu também estou colando o código em http://dpaste.com/113388/ porque pode ter números de linha e este material remarcação está me dando uma dor de cabeça.

Atualização de 27 de janeiro de 15:52 EST: Ligeiramente código atualizado pode ser encontrado aqui: http://drop.io/threagui / ativo / thread-gui-rev3-tar-gz

Traceback

crashsystems@crashsystems-laptop:~/Desktop/thread-gui$ python threadgui.pybtnStartStop clicked
Traceback (most recent call last):
  File "threadgui.py", line 39, in on_btnStartStop_clicked
    self.thread.stop()
  File "threadgui.py", line 20, in stop
    self.join()
  File "/usr/lib/python2.5/threading.py", line 583, in join
    raise RuntimeError("cannot join thread before it is started")
RuntimeError: cannot join thread before it is started
btnStartStop clicked
threadStop = 1
btnStartStop clicked
threadStop = 0
btnStartStop clicked
Traceback (most recent call last):
  File "threadgui.py", line 36, in on_btnStartStop_clicked
    self.thread.start()
  File "/usr/lib/python2.5/threading.py", line 434, in start
    raise RuntimeError("thread already started")
RuntimeError: thread already started
btnExit clicked
exit() called

Código

#!/usr/bin/bash
import gtk, threading

class ThreadLooper (threading.Thread):
    def __init__ (self, sleep_interval, function, args=[], kwargs={}):
        threading.Thread.__init__(self)
        self.sleep_interval = sleep_interval
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.finished = threading.Event()

    def stop (self):
        self.finished.set()
        self.join()

    def run (self):
        while not self.finished.isSet():
            self.finished.wait(self.sleep_interval)
            self.function(*self.args, **self.kwargs)

class ThreadGUI:
    # Define signals
    def on_btnStartStop_clicked(self, widget, data=None):
        print "btnStartStop clicked"
        if(self.threadStop == 0):
            self.threadStop = 1
            self.thread.start()
        else:
            self.threadStop = 0
            self.thread.stop()
        print "threadStop = " + str(self.threadStop)

    def on_btnMessageBox_clicked(self, widget, data=None):
        print "btnMessageBox clicked"
        self.lblMessage.set_text("This is a message!")
        self.msgBox.show()

    def on_btnExit_clicked(self, widget, data=None):
        print "btnExit clicked"
        self.exit()

    def on_btnOk_clicked(self, widget, data=None):
        print "btnOk clicked"
        self.msgBox.hide()

    def on_mainWindow_destroy(self, widget, data=None):
        print "mainWindow destroyed!"
        self.exit()

    def exit(self):
        print "exit() called"
        self.threadStop = 1
        gtk.main_quit()

    def threadLoop(self):
        # This will run in a thread
        self.txtThreadView.set_text(str(self.threadCount))
        print "hello world"
        self.threadCount += 1

    def __init__(self):
        # Connect to the xml GUI file
        builder = gtk.Builder()
        builder.add_from_file("threadgui.xml")

        # Connect to GUI widgets
        self.mainWindow = builder.get_object("mainWindow")

        self.txtThreadView = builder.get_object("txtThreadView")
        self.btnStartStop = builder.get_object("btnStartStop")
        self.msgBox = builder.get_object("msgBox")
        self.btnMessageBox = builder.get_object("btnMessageBox")
        self.btnExit = builder.get_object("btnExit")
        self.lblMessage  = builder.get_object("lblMessage")
        self.btnOk = builder.get_object("btnOk")

        # Connect the signals
        builder.connect_signals(self)

        # This global will be used for signaling the thread to stop.
        self.threadStop = 1

        # The thread
        self.thread = ThreadLooper(0.1, self.threadLoop, (1,0,-1))
        self.threadCounter = 0

if __name__ == "__main__":
    # Start GUI instance
    GUI = ThreadGUI()
    GUI.mainWindow.show()
    gtk.main()
Foi útil?

Solução

Enfiar com PyGTK é um pouco complicado se você quiser fazer isso direito. Basicamente, você não deve atualizar GUI a partir de qualquer outro segmento do thread principal (limitação comum em libs GUI). Geralmente isso é feito em PyGTK usando o mecanismo de mensagens em fila (para comunicação entre trabalhadores e GUI) que são lidos periodicamente utilizando a função timeout. Uma vez tive uma apresentação sobre minha LUG local sobre este assunto, você pode pegar exemplo de código para esta apresentação de Google Code repositório. Ter um olhar para classe MainWindow em forms/frmmain.py, especialmente para _pulse() método e que é feito em on_entry_activate() (thread é iniciada há mais o temporizador de inactividade é criado).

def on_entry_activate(self, entry):
    text = entry.get_text().strip()
    if text:
        store = entry.get_completion().get_model()
        if text not in [row[0] for row in store]:
            store.append((text, ))
        thread = threads.RecommendationsFetcher(text, self.queue)# <- 1
        self.idle_timer = gobject.idle_add(self._pulse)# <- 2
        tv_results = self.widgets.get_widget('tv_results')
        model = tv_results.get_model()
        model.clear()
        thread.setDaemon(True)# <- 3
        progress_update = self.widgets.get_widget('progress_update')
        progress_update.show()
        thread.start()# <- 4

Desta forma, a aplicação atualiza GUI quando é "idle" (por meio do GTK) sem causar congela.

  • 1: criar fio
  • 2: criar temporizador de inactividade
  • 3: daemonize fio para que o aplicativo pode ser fechado sem esperar pela conclusão fio
  • 4: rosca início

Outras dicas

Geralmente é melhor evitar tópicos quando você pode. É muito difícil escrever um aplicativo enfiada corretamente, e ainda mais difícil saber que você acertou. Desde que você está escrevendo um aplicativo GUI, é mais fácil para que você possa visualizar como a fazê-lo, desde que você já tem que escrever sua aplicação dentro de uma estrutura assíncrona.

O que é importante perceber é que uma aplicação GUI está fazendo um monte de nada. Ele passa a maior parte de seu tempo de espera para o sistema operacional para dizer-lhe que algo aconteceu. Você pode fazer um monte de coisas neste tempo ocioso enquanto você sabe como escrever código de longa duração para que ele não bloquear.

Você pode resolver o seu problema original utilizando um tempo limite; dizendo a seu quadro GUI para chamar de volta alguma função depois de um atraso, e, em seguida, redefinir esse atraso ou iniciar outra chamada atrasada.

Outra pergunta comum é como se comunicar através da rede em uma aplicação gráfica. aplicações de rede são como GUI aplicativos em que eles fazem um monte de espera. Usando um quadro IO rede (como torcida ) faz com que seja fácil ter ambas as partes de sua espera aplicativo cooperativamente em vez de forma competitiva, e novamente alivia a necessidade de fios extras.

cálculos de execução longa pode ser escrito de forma iterativa em vez de forma síncrona, e você pode fazer o seu processamento, enquanto a GUI está ocioso. Você pode usar um gerador para fazer isso muito facilmente em python.

def long_calculation(param, callback):
    result = None
    while True:
        result = calculate_next_part(param, result)
        if calculation_is_done(result):
            break
        else:
            yield
    callback(result)

Chamando long_calculation lhe dará um objeto gerador, e chamando .next() no objeto gerador irá executar o gerador até que ele atinja tanto yield ou return. Você poderia simplesmente dizer a estrutura GUI para long_calculation(some_param, some_callback).next chamada quando tem tempo, e, eventualmente, o retorno de chamada será chamado com o resultado.

Eu não sei GTK muito bem, então eu não posso dizer-lhe que gobject funções que deve ser chamada. Com esta explicação, porém, você deve ser capaz de encontrar as funções necessárias na documentação, ou na pior das hipóteses, pergunte em um canal IRC relevante.

Infelizmente não há nenhuma resposta boa-caso geral. Se você esclarecer exatamente o que você está tentando fazer, que seria mais fácil para explicar por que você não precisa de fios nessa situação.

Você não pode reiniciar um objeto Thread parado; não tente. Em vez disso, criar uma nova instância do objeto se você quiser reiniciá-lo depois que ele é realmente parou e juntou-se.

Eu tenho jogado com diferentes ferramentas para ajudar a limpar o trabalho com fios, processamento ocioso, etc.

make_idle é um decorador função que permite que você execute uma tarefa em segundo plano cooperativamente. Este é um bom meio termo entre algo curto o suficiente para ser executado uma vez no segmento interface do usuário e não afeta a capacidade de resposta do aplicativo e fazer uma linha para fora completa na sincronização especial. Dentro da função decorado você usar "rendimento" para entregar o processamento volta para o GUI para que ele possa permanecer ágil e da próxima vez que a interface do usuário está ocioso ele vai pegar em sua função de onde você parou. Então, para começar com isso você só chamar idle_add para a função decorados.

def make_idler(func):
    """
    Decorator that makes a generator-function into a function that will
continue execution on next call
    """
    a = []

    @functools.wraps(func)
    def decorated_func(*args, **kwds):
        if not a:
            a.append(func(*args, **kwds))
        try:
            a[0].next()
            return True
        except StopIteration:
            del a[:]
            return False

    return decorated_func

Se você precisar de fazer mais processamento de um bit, você pode usar um gerenciador de contexto para bloquear o segmento interface do usuário sempre que necessário para ajudar a tornar o código um pouco mais seguro

@contextlib.contextmanager
def gtk_critical_section():
    gtk.gdk.threads_enter()
    try:
        yield
    finally:
        gtk.gdk.threads_leave()

com que você pode simplesmente

with gtk_critical_section():
    ... processing ...

Eu não terminei com ele ainda, mas na combinação de fazer as coisas puramente em idle e puramente em um fio, eu tenho um decorador (ainda não testado por isso não publicado), que você pode dizer que se a próxima seção após o rendimento é para ser executado no tempo ocioso da interface do usuário ou em um fio. Isso permitiria um para fazer alguma configuração no segmento interface do usuário, mude para um novo segmento para fazer o material de fundo e, em seguida, passar para o tempo ocioso da interface do usuário para fazer a limpeza, minimizando a necessidade de bloqueios.

Eu não olhei em detalhes em seu código. Mas eu vejo duas soluções para o seu problema:

Não use tópicos em tudo. Em vez disso usar um tempo limite, assim:

import gobject

i = 0
def do_print():
    global i
    print i
    i += 1
    if i == 10:
        main_loop.quit()
        return False
    return True

main_loop = gobject.MainLoop()
gobject.timeout_add(250, do_print)
main_loop.run()

Ao usar threads, você deve se certificar de que seu código GUI só é chamado de um thread, ao mesmo tempo, protegendo-o assim:

import threading
import time

import gobject
import gtk

gtk.gdk.threads_init()

def run_thread():
    for i in xrange(10):
        time.sleep(0.25)
        gtk.gdk.threads_enter()
        # update the view here
        gtk.gdk.threads_leave()
    gtk.gdk.threads_enter()
    main_loop.quit()
    gtk.gdk.threads_leave()

t = threading.Thread(target=run_thread)
t.start()
main_loop = gobject.MainLoop()
main_loop.run()
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top