2011-12-21 3 views
5

Ho un processo che richiederà un po '(forse un minuto o due) per completare. Quando chiamo questo dalla mia GUI pygtk la finestra si blocca (si scurisce e impedisce l'azione dell'utente) dopo circa 10 secondi.Arresta la GUI pygtk dal blocco durante il processo di lunga durata

Mi piacerebbe evitare che ciò accada, ma non so come. Pensavo che il multithreading sarebbe stata la risposta, ma non sembra funzionare. Ho provato due metodi diversi che ho trovato online. Innanzitutto, ho modificato this FAQ per eseguire una funzione di esecuzione prolungata. In secondo luogo ho provato a utilizzare threading.Thread direttamente come in this answer, ma anche questo si blocca.

I miei due campioni sono sotto. Sono nuovo nel multithreading, quindi forse non è la soluzione che sto cercando. In pratica sto solo provando a mantenere la GUI bloccata, così posso aggiornare con una barra di avanzamento e lasciare che l'utente usi un pulsante di annullamento.

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

risposta

7

Di seguito riportiamo una versione modificata del secondo esempio che funziona per me:

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

Le modifiche apportate sono:

  • Evitare di attesa nel callback per il filo alla fine per mantenere il eventi di elaborazione del ciclo principale.
  • Barra di avanzamento aggiunta da visualizzare quando viene eseguito un thread.
  • Utilizzato glib.timeout_add per pianificare una richiamata che pulsa la barra di avanzamento quando viene eseguito un determinato thread. Ciò ha lo stesso effetto del polling del thread, ma con il vantaggio che il loop principale è ancora sensibile ad altri eventi.
  • Utilizzato threading.Lock per dimostrare che il callback deve essere programmato più volte, indipendentemente dal numero di volte in cui si fa clic sul pulsante.
  • Aggiunto gobject.threads_init che mancava in questo esempio (non nel precedente).

Ora, quando si fa clic sul pulsante, si vedrà come si fa clic sull'etichetta e la barra di avanzamento pulsa fintanto che un thread è in esecuzione.

+0

Ok, questo ha senso. Capisco perché non riesco a sondare costantemente lo stato del thread. Ma ho bisogno di sapere quando finisce il thread. Se aggiungo una barra di stato per l'avanzamento del thread, come posso sapere quando interrompere la barra di stato? –

+0

Ho aggiunto una barra di avanzamento all'esempio. 'glib.timeout_add' consente di eseguire il polling dello stato del thread senza rendere l'appplicazione non rispondente. – jcollado

+0

Ok, bene. Questo ha senso. Non sapevo di 'glib.timeout_add'. Grazie per l'aiuto. –

0

Si dovrebbe reimplementare Thread.run per ciascuno dei vostri fili, e avviare un ciclo di eventi in loro.

Inoltre, è possibile premere il pulsante per chiamare il metodo start per un thread, che chiamerà quindi run e svolgerà l'attività lunga. In questo modo, non è necessario un ciclo di eventi in ogni thread.

Ecco il codice semplice da spiegare cosa intendo per la seconda opzione:

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) 

Io uso una funzione di callback perché dubito che set_text è thread-safe.

+0

Spiacente, ho incollato lo stesso esempio due volte. Nel secondo esempio, ho il pulsante chiamata thread.start(), quindi attendo che il thread termini. Ciò blocca ancora la mia applicazione. –

+0

Nel secondo esempio si sta eseguendo il polling con 'thread.is_alive' all'interno di un metodo di callback. Ciò fa sì che il callback impieghi tutto il tempo necessario per completare il thread stesso e l'applicazione non elabora altri eventi fino a quando non termina la richiamata. – jcollado

+0

@ d-k Mi interessa l'opzione di eseguire un ciclo di eventi in ogni thread perché non l'ho mai visto nel codice reale.Potresti approfondire su quello? – jcollado