28 novembre, 2006

Threads e gui pygtk: Soluzione definitiva

Se avete letto questo post vi avevo spiegato come usare
gtk.gdk.threads_init(), gtk.gdk.threads_enter() e
gtk.gdk.threads_leave().
Nel post successivo vi ho spiegato che quell'approccio però
penalizza la portabilità su windows. Ora per rendere il nostro codice
portabile potremmo utilizzare due diverse soluzioni. La prima che vi
propongo la mostro solo per completezza ma la sconsiglio e poi vi
spiegherò perché. La seconda è invece la più sicura ed è garanzia
di funzionamento anche in situazioni con più threads attivi.
Allora la prima soluzione consiste nell'eseguire le funzioni che danno
problemi con una gobject.idle_add() e "circondare" il main
con gtk.gdk.threads_enter() e gtk.gdk.threads_leave()
Il codice diventa (date uno sguardo qui per capire le differenze):


import threading
import time
import gtk
import gobject


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 :
gtk.gdk.threads_enter()
# uso la idle_add
gobject.idle_add(self.pbar.set_fraction, t)
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.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()

Questo codice funziona anche su windows ma se i threads iniziano a diventare tanti ( ma non solo ) spesso si hanno dei problemi. Quindi se non volete impazzire vi consiglio vivamente questa seconda soluzione che consiste nell'evitare come la morte le varie threads_enter, threads_leave e init_threads e fare in modo che tutte le operazioni che hanno a che fare con le gtk vengano eseguite dallo stesso thread. L'esempio precedente può essere riscritto in questo modo:

import threading
import time
import gtk


class MyThread(threading.Thread):
def __init__(self, main):
self.main = main
threading.Thread.__init__(self)

def run(self):
while not self.main.stop :
self.main.pbar_fraction += 0.1
if self.main.pbar_fraction > 1.0:
self.main.pbar_fraction = 0.0

time.sleep(0.5)

class Main:
def __init__(self):
self.pbar_fraction = 0
self.stop = False

window = gtk.Window()
self.pbar = gtk.ProgressBar()
window.add(self.pbar)
window.show_all()
window.connect('destroy', self.exit)

th = MyThread(self)
th.start()

self.main_loop()

def exit(self, arg):
self.stop = True

def main_loop(self):
while not self.stop:
self.pbar.set_fraction(self.pbar_fraction)
while gtk.events_pending():
gtk.main_iteration()
time.sleep(0.01)

Main()

Come vedete non abbiamo usato neppure il gtk.main() e
il gtk.main_quit() ma facciamo tutto da noi ;)
Questo codice è portabile, funzionante e non vi farà imprecare :)

8 commenti:

  1. Anonimo12:52 PM

    ciao,
    hai mai provato la libreria wxPython?
    la trovi migliore o peggiore delle pyGtk?

    Ah, è molto interessante il tuo blog, soprattutto per la roba su python, che ho deciso di imparare :)

    RispondiElimina
  2. Non ho mai provato wxPython per il semplice motivo che non è quella predefinita per il desktop Gnome e visto che scrivo principalmente per Gnome utilizzo le pygtk ;)

    Probabilmente le wxPython si integreranno meglio su windows (se non ricordo male)
    Quindi se pensi di scrivere software per windows che deve funzionare anche su linux vai con le wxPython se invece vuoi (come me) scrivere software per linux che possa funzionare anche su windows vai con le pygtk

    RispondiElimina
  3. Anonimo9:02 AM

    io porterei il metodo run
    sotto l'applicazione Main
    cosi' da rendere indipendente
    il modulo MyThread

    es:
    class MyThread(threading.Thread):
    def __init__(self, main, run):
    self.main = main
    self.run = run
    threading.Thread.__init__(self)

    e su Main:
    # thread
    th = MyThread(self, self.run)

    ciao Loris

    RispondiElimina
  4. Anonimo6:18 PM

    ciao io ho un problema simile con i threads e gtk, mi spiego: ho una gui(filetransfer) e all'interno c'è una progressbar che si deve aggiornare; uso un thread a parte per aggiornare la progress...questo mi permetteva di non far bloccare la gui(grazie appunto a gtk.gdk.threads_init()) mentre la progress avanzava, ora non so proprio come organizzarmi..., che soluzioni mi suggerireste?

    RispondiElimina
  5. bellissimo post: mi ha salvato la vita...

    avevo un thread e un widget con gtkmozembed... ad ogni colpo di thread era un crash...

    Molto bello python ma il supoprto ai thread è veramente scarso!

    RispondiElimina
  6. Anonimo1:25 AM

    Confermo la bontà della soluzione, un solo appunto, la funzione main_loop è parecchio più lenta rispetto al classico gtk.main(), hai qualche soluzione?

    RispondiElimina
  7. Il secondo metodo funziona molto bene, ma è un po'pesante. Forse negli script che "aspettano" tanto (come quando si aspetta molto tempo con uno sleep o quando si aspetta la risposta su un socket) sarebbe meglio usare un tracer anziché effettuare cento controlli al secondo, ma forse risulterebbe troppo complesso.

    Spero solo che un giorno la funzione gtk.gdk.threads_init() funzioni anche su windows, perché ho scelto pygtk proprio per avere una libreria cross-platform.

    RispondiElimina
  8. Anonimo9:46 AM

    Hello
    acomplia 20 mg
    It was October 2008, where European Medicines Agency concluded that benefits of Acomplia no longer outweighed its risks and it is recommended to be withdrawed in the market.
    [url=http://www.jomtienbeachhotel.com/]acomplia diet pills[/url]
    It must be taken on empty stomach for faster absorption and it is suggested to avoid eating breakfast for 1 hour after taking the medication.
    http://www.jomtienbeachhotel.com/ - acomplia buy uk
    It switches off the brain circuits that make the people hungry.

    RispondiElimina