2011-12-21 9 views
5

Mam proces, który potrwa chwilę (może minutę lub dwie), aby zakończyć. Kiedy wywołuję to z mojego GUI pygtk, okno blokuje się (przyciemnia i zapobiega działaniom użytkownika) po około 10 sekundach.Zatrzymaj GUI Pygtk przed blokowaniem podczas długotrwałego procesu

Chciałbym temu zapobiec, ale nie jestem pewien jak. Myślałem, że wielowątkowość będzie odpowiedzią, ale wydaje się, że nie działa. Próbowałem dwóch różnych metod, które znalazłem online. Najpierw zmodyfikowałem this FAQ, aby uzyskać długą funkcjonalność. Po drugie próbowałem używać threading.Thread bezpośrednio jak w this answer, ale to również się blokuje.

Moje dwie próbki znajdują się poniżej. Jestem nowy w wielowątkowości, więc może to nie jest rozwiązanie, którego szukam. Zasadniczo próbuję powstrzymać GUI przed blokowaniem, więc mogę aktualizować za pomocą paska postępu i pozwolić użytkownikowi na użycie przycisku anulowania.

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

Odpowiedz

7

Poniżej zamieszczamy zmodyfikowanej wersji drugim przykładzie, który pracuje dla mnie:

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

dokonanych zmian są:

  • uniknąć oczekiwania w zwrotnego dla wątku do końca, aby utrzymać zdarzenia przetwarzania pętli głównej.
  • Dodano pasek postępu do wyświetlenia, gdy wątek jest wykonywany.
  • Zastosowano glib.timeout_add, aby zaplanować wywołanie zwrotne, które pulsuje pasek postępu, gdy wykonywany jest wątek. Ma to taki sam efekt, jak odpytywanie wątku, ale z tą zaletą, że główna pętla nadal reaguje na inne zdarzenia.
  • Używane threading.Lock do sprawdzania, czy wywołanie zwrotne ma być zaplanowane więcej niż jeden raz, niezależnie od tego, ile razy kliknięto przycisk.
  • Dodano gobject.threads_init, którego brakowało w tym przykładzie (nie w poprzednim).

Teraz po kliknięciu przycisku zobaczysz, jak kliknięta jest etykieta, a pasek postępu pulsuje tak długo, jak wątek jest uruchomiony.

+0

OK, to ma sens. Rozumiem, dlaczego nie mogę ciągle sprawdzać statusu wątku. Ale muszę wiedzieć, kiedy wątek się skończy. Jeśli dodaję pasek stanu dla postępu wątku, skąd mogę wiedzieć, kiedy zatrzymać pasek stanu? –

+0

Dodałem pasek postępu do przykładu. 'glib.timeout_add' pozwala ci odpytywać status wątku bez powodowania braku reakcji aplikacji. – jcollado

+0

Okay świetnie. To ma sens. Nie wiedziałem o 'glib.timeout_add'. Dzięki za pomoc. –

0

Należy reimplement Thread.run dla każdego z wątków i uruchomić pętlę zdarzeń w nich.

Można również nacisnąć przycisk wywołania metody start dla wątku, który następnie wywoła run i wykonać długie zadanie. W ten sposób nie potrzebujesz pętli zdarzeń w każdym wątku.

Oto prosty kod, aby wyjaśnić, co to znaczy dla drugiej opcji:

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) 

używam funkcji zwrotnej, ponieważ wątpię, że set_text jest bezpieczny wątku.

+0

Przykro mi, przykleiłem ten sam przykład dwa razy. W drugim przykładzie mam przycisk wywołania thread.start(), a następnie czekam na zakończenie wątku. To nadal blokuje moją aplikację. –

+0

W drugim przykładzie odpytujesz 'thread.is_alive' wewnątrz metody wywołania zwrotnego. To powoduje, że wywołanie zwrotne zajmuje tyle samo czasu, co sam wątek, a aplikacja nie przetwarza żadnego innego zdarzenia do czasu zakończenia wywołania zwrotnego. – jcollado

+0

@ d-k Jestem zainteresowany opcją uruchomienia pętli zdarzeń w każdym wątku, ponieważ nigdy nie widziałem tego w prawdziwym kodzie.Czy mógłbyś rozwinąć ten temat? – jcollado

Powiązane problemy