23 novembre, 2006

Threads e gui pygtk in python

Se vi è capitato di programmare con le gtk in python (e non solo) utilizzando i threads, vi sarete accorti che spesso vengono fuori problemi.

Perchè quel maledetto thread non riesce ad aggiornare la gui?

Facciamo un semplice esempio per capirci meglio.


import threading
import time
import gtk

class MyThread(threading.Thread):
def __init__(self, pbar):
self.pbar = pbar
self.stop = False
threading.Thread.__init__(self)

def run(self):
t = 0
while not self.stop :
self.pbar.set_fraction(t)

t = t + 0.1
if t > 1.0:
t = 0.0

time.sleep(0.5)


def exit(arg):
th.stop = True
gtk.main_quit()

window = gtk.Window()
pbar = gtk.ProgressBar()
window.add(pbar)
window.show_all()

window.connect('destroy', exit)

th = MyThread(pbar)
th.start()

gtk.main()


Questo semplice programma non fa altro che creare una finestra gtk, aggiungergli un widget progressbar e creare un thread che aggiorna il progresso ogni mezzo secondo. Beh se provate ad eseguirlo, la finestra con la progress bar verrà creata ma non ci sarà alcun aggiornamento.

Il problema sta nel fatto che il thread, prova ad operare sull'oggetto gtk. Per fare ciò le librerie gtk prevedono una procedura particolare.
Prima di tutto va richiamata la funzione gtk.gdk.threads_init() prima della chiamata a gtk.main(). Poi ogni volta che si vuole accedere ad un oggetto gtk vanno usate le due funzioni:
gtk.gdk.threads_enter()
gtk.gdk.threads_leave()
Il codice corretto quindi è:

import threading
import time
import gtk

# chiamo threads_init()
gtk.gdk.threads_init()

class MyThread(threading.Thread):
def __init__(self, pbar):
self.pbar = pbar
self.stop = False
threading.Thread.__init__(self)

def run(self):
t = 0
while not self.stop:
# threads_enter
gtk.gdk.threads_enter()
self.pbar.set_fraction(t)
# threads_leave
gtk.gdk.threads_leave()

t = t + 0.1
if t > 1.0:
t = 0.0

time.sleep(0.5)


def exit(arg):
th.stop = True
gtk.main_quit()

window = gtk.Window()
pbar = gtk.ProgressBar()
window.add(pbar)
window.show_all()

window.connect('destroy', exit)

th = MyThread(pbar)
th.start()

gtk.main()


Se lo eseguite vedrete la nostra progress bar finalmente in funzione!

1 commento: