2011-12-21 27 views
5

Tengo un proceso que tomará un tiempo (quizás uno o dos minutos) en completarse. Cuando llamo esto desde mi GUI de pygtk, la ventana se bloquea (se oscurece y evita la acción del usuario) después de unos 10 segundos.Detener la GUI de pygtk para que no se bloquee durante el proceso de larga ejecución

Me gustaría evitar que esto suceda, pero no estoy seguro de cómo. Pensé que la multi-lectura sería la respuesta, pero parece que no funciona. Probé dos métodos diferentes que encontré en línea. Primero, modifiqué this FAQ para tomar una función de larga ejecución. En segundo lugar, traté de usar threading. Thread directamente como en this answer, pero eso también se bloquea.

Mis dos muestras están por debajo. Soy nuevo en multihilo, así que tal vez no sea la solución que estoy buscando. Básicamente estoy tratando de evitar que la GUI se bloquee, así que puedo actualizar con una barra de progreso y dejar que el usuario use un botón de cancelar.

#Sample 1 
import threading 
import time 
import gobject 
import gtk 

gobject.threads_init() 

class MyThread(threading.Thread): 
    def __init__(self, label, button): 
     super(MyThread, self).__init__() 
     self.label = label 
     self.button = button 
     self.counter = 0 
     button.connect("clicked", self.on_button_click) 
     self.quit = False 

    def update_label(self, counter): 
     self.label.set_text("Counter: %i" % counter) 
     time.sleep(20) 
     return False 

    def on_button_click(self, widget): 
     self.counter += 1 
     gobject.idle_add(self.update_label, self.counter) 

window = gtk.Window() 
label = gtk.Label() 
box = gtk.VBox() 
button = gtk.Button("Test") 
box.pack_start(label) 
box.pack_start(button) 
window.add(box) 
window.show_all() 
window.connect("destroy", lambda _: gtk.main_quit()) 
thread = MyThread(label, button) 
thread.start() 

gtk.main() 
thread.quit = True 

##################################### 
#Sample 2 

from threading import Thread 
import time 
import gobject 
import gtk 

class Test(): 
    def __init__(self): 
     self.counter = 0 
     self.label = gtk.Label() 
     button = gtk.Button("Test") 

     window = gtk.Window() 
     box = gtk.VBox() 
     box.pack_start(self.label) 
     box.pack_start(button) 
     window.add(box) 

     window.connect("destroy", lambda _: gtk.main_quit()) 
     button.connect("clicked", self.on_button_click) 
     window.show_all() 

    def update_label(self, counter): 
     self.label.set_text("Counter: %i" % counter) 
     time.sleep(20) 
     return False 

    def on_button_click(self, widget): 
     self.counter += 1 
     thread = Thread(target=self.update_label, args=(self.counter,)) 
     thread.start() 
     while thread.is_alive(): 
      pass 
     thread.stop() 

test = Test() 
gtk.main() 

Respuesta

7

A continuación una versión modificada del segundo ejemplo que funciona para mí:

import threading 
import time 
import gtk, gobject, glib 

gobject.threads_init() 

class Test(): 
    def __init__(self): 
     self.counter = 0 
     self.label = gtk.Label() 
     self.progress_bar = gtk.ProgressBar() 
     self.progress_bar_lock = threading.Lock() 
     button = gtk.Button("Test") 

     window = gtk.Window() 

     box = gtk.VBox() 
     box.pack_start(self.label) 
     box.pack_start(self.progress_bar) 
     box.pack_start(button) 
     window.add(box) 

     window.connect("destroy", lambda _: gtk.main_quit()) 
     button.connect("clicked", self.on_button_click) 
     window.show_all() 

    def update_label(self, counter): 
     self.label.set_text("Thread started (counter: {0})" 
          .format(counter)) 
     time.sleep(5) 
     self.label.set_text("Thread finished (counter: {0})" 
          .format(counter)) 
     return False 

    def pulse_progress_bar(self): 
     print threading.active_count() 
     if threading.active_count() > 1: 
      self.progress_bar.pulse() 
      return True 

     self.progress_bar.set_fraction(0.0) 
     self.progress_bar_lock.release() 
     return False 

    def on_button_click(self, widget): 
     self.counter += 1 
     thread = threading.Thread(target=self.update_label, 
            args=(self.counter,)) 
     thread.start() 

     if self.progress_bar_lock.acquire(False): 
      glib.timeout_add(250, self.pulse_progress_bar) 


if __name__ == '__main__': 
    test = Test() 
    gtk.main() 

Los cambios realizados son:

  • evitar tener que esperar en la devolución de llamada para el hilo a fin de mantener el eventos de procesamiento de bucle principal.
  • Barra de progreso agregada para mostrar cuando se está ejecutando un hilo.
  • Se usó glib.timeout_add para programar una devolución de llamada que activa la barra de progreso cuando se está ejecutando algún subproceso. Esto tiene el mismo efecto que el sondeo del hilo, pero con la ventaja de que el bucle principal sigue respondiendo a otros eventos.
  • Usado threading.Lock para evitar que la devolución de llamada se programe más de una vez, independientemente de cuántas veces se haga clic en el botón.
  • Agregado gobject.threads_init que faltaba en este ejemplo (no en el anterior).

Ahora, al hacer clic en el botón, verá cómo se hace clic en la etiqueta y se pulsa la barra de progreso mientras se está ejecutando un hilo.

+0

De acuerdo, esto tiene sentido. Entiendo por qué no puedo buscar constantemente el estado del hilo. Pero DEBO saber cuándo termina el hilo. Si agrego una barra de estado para el progreso del hilo, ¿cómo puedo saber cuándo detener la barra de estado? –

+0

He agregado una barra de progreso al ejemplo. 'glib.timeout_add' le permite sondear el estado del hilo sin hacer que su aplicación no responda. – jcollado

+0

Muy bien. Esto tiene sentido. No sabía sobre 'glib.timeout_add'. Gracias por tu ayuda. –

0

Debe reimplementar Thread.run para cada uno de sus hilos, e iniciar un ciclo de eventos en ellos.

Además, puede presionar el botón para llamar al método start para un hilo, que luego llamará al run, y haga su larga tarea. De esta forma, no necesita un bucle de evento en cada hilo.

Aquí hay un código simple de explicar lo que quiero decir por la segunda opción:

class MyThread(threading.Thread): 

    def __init__(self, label, button): 
     threading.Thread.__init__(self) 
     self.label = label 
     self.button = button 
     self.counter = 0 

    def run(self): 
     time.sleep(20) 

def callback(): 
    label.set_text("Counter: %i" % thread.counter) 
    thread.start() 

window = gtk.Window() 
label = gtk.Label() 
box = gtk.VBox() 
button = gtk.Button('Test') 
box.pack_start(label) 
box.pack_start(button) 
window.add(box) 
window.show_all() 

thread = MyThread(label, button) 
button.connect('clicked', callback) 

utilizo una función de devolución de llamada porque dudo que set_text es seguro para subprocesos.

+0

Lo siento, pegué el mismo ejemplo dos veces. En el segundo ejemplo, tengo el botón call thread.start(), luego espero a que termine el hilo. Esto todavía bloquea mi aplicación. –

+0

En el segundo ejemplo estás encuestando con 'thread.is_alive' dentro de un método de devolución de llamada. Esto hace que la devolución de llamada tarde tanto tiempo en completarse como el propio subproceso y que su aplicación no esté procesando ningún otro evento hasta que finalice la devolución de llamada. – jcollado

+0

@ d-k Estoy interesado en la opción de ejecutar un bucle de evento en cada hilo porque nunca lo he visto en código real.¿Podrías profundizar en eso? – jcollado

Cuestiones relacionadas