Domanda

Come nuovo per lo sviluppo della GUI in Python (con pyGTK), ho appena iniziato a conoscere il threading. Per testare le mie capacità, ho scritto una semplice piccola interfaccia GTK con un pulsante di avvio / arresto. L'obiettivo è che quando viene cliccato, inizia un thread che aumenta rapidamente un numero nella casella di testo, mantenendo la GUI reattiva.

La GUI funziona bene, ma sto riscontrando problemi con il threading. Probabilmente è un problema semplice, ma la mia mente è fritta per il giorno. Di seguito ho incollato prima il trackback dell'interprete Python, seguito dal codice. Puoi andare su http://drop.io/pxgr5id per scaricarlo. Sto usando bzr per il controllo di revisione, quindi se si desidera apportare una modifica e rilasciarla nuovamente, si prega di confermare le modifiche. Sto anche incollando il codice su http://dpaste.com/113388/ perché può avere numeri di riga e questa roba del ribasso mi sta facendo venire il mal di testa.

Aggiornamento 27 gennaio, 15:52 EST: Codice leggermente aggiornato può essere trovato qui: http://drop.io/threagui / attività / thread-gui-rev3-tar-gz

Traceback

crashsystems@crashsystems-laptop:~/Desktop/thread-gui$ python threadgui.pybtnStartStop clicked
Traceback (most recent call last):
  File "threadgui.py", line 39, in on_btnStartStop_clicked
    self.thread.stop()
  File "threadgui.py", line 20, in stop
    self.join()
  File "/usr/lib/python2.5/threading.py", line 583, in join
    raise RuntimeError("cannot join thread before it is started")
RuntimeError: cannot join thread before it is started
btnStartStop clicked
threadStop = 1
btnStartStop clicked
threadStop = 0
btnStartStop clicked
Traceback (most recent call last):
  File "threadgui.py", line 36, in on_btnStartStop_clicked
    self.thread.start()
  File "/usr/lib/python2.5/threading.py", line 434, in start
    raise RuntimeError("thread already started")
RuntimeError: thread already started
btnExit clicked
exit() called

Codice

#!/usr/bin/bash
import gtk, threading

class ThreadLooper (threading.Thread):
    def __init__ (self, sleep_interval, function, args=[], kwargs={}):
        threading.Thread.__init__(self)
        self.sleep_interval = sleep_interval
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.finished = threading.Event()

    def stop (self):
        self.finished.set()
        self.join()

    def run (self):
        while not self.finished.isSet():
            self.finished.wait(self.sleep_interval)
            self.function(*self.args, **self.kwargs)

class ThreadGUI:
    # Define signals
    def on_btnStartStop_clicked(self, widget, data=None):
        print "btnStartStop clicked"
        if(self.threadStop == 0):
            self.threadStop = 1
            self.thread.start()
        else:
            self.threadStop = 0
            self.thread.stop()
        print "threadStop = " + str(self.threadStop)

    def on_btnMessageBox_clicked(self, widget, data=None):
        print "btnMessageBox clicked"
        self.lblMessage.set_text("This is a message!")
        self.msgBox.show()

    def on_btnExit_clicked(self, widget, data=None):
        print "btnExit clicked"
        self.exit()

    def on_btnOk_clicked(self, widget, data=None):
        print "btnOk clicked"
        self.msgBox.hide()

    def on_mainWindow_destroy(self, widget, data=None):
        print "mainWindow destroyed!"
        self.exit()

    def exit(self):
        print "exit() called"
        self.threadStop = 1
        gtk.main_quit()

    def threadLoop(self):
        # This will run in a thread
        self.txtThreadView.set_text(str(self.threadCount))
        print "hello world"
        self.threadCount += 1

    def __init__(self):
        # Connect to the xml GUI file
        builder = gtk.Builder()
        builder.add_from_file("threadgui.xml")

        # Connect to GUI widgets
        self.mainWindow = builder.get_object("mainWindow")

        self.txtThreadView = builder.get_object("txtThreadView")
        self.btnStartStop = builder.get_object("btnStartStop")
        self.msgBox = builder.get_object("msgBox")
        self.btnMessageBox = builder.get_object("btnMessageBox")
        self.btnExit = builder.get_object("btnExit")
        self.lblMessage  = builder.get_object("lblMessage")
        self.btnOk = builder.get_object("btnOk")

        # Connect the signals
        builder.connect_signals(self)

        # This global will be used for signaling the thread to stop.
        self.threadStop = 1

        # The thread
        self.thread = ThreadLooper(0.1, self.threadLoop, (1,0,-1))
        self.threadCounter = 0

if __name__ == "__main__":
    # Start GUI instance
    GUI = ThreadGUI()
    GUI.mainWindow.show()
    gtk.main()
È stato utile?

Soluzione

Il threading con PyGTK è un po 'complicato se vuoi farlo nel modo giusto. Fondamentalmente, non dovresti aggiornare la GUI da nessun altro thread oltre al thread principale (limitazione comune nelle librerie della GUI). Normalmente questo viene fatto in PyGTK usando il meccanismo dei messaggi in coda (per la comunicazione tra i lavoratori e la GUI) che vengono letti periodicamente usando la funzione di timeout. Una volta che ho avuto una presentazione sul mio LUG locale su questo argomento, puoi prendere il codice di esempio per questa presentazione da Codice Google repository. Dai un'occhiata alla MainWindow classe in forms/frmmain.py, specialmente per il metodo _pulse() e cosa viene fatto in on_entry_activate() (il thread viene avviato lì e viene creato il timer di inattività).

def on_entry_activate(self, entry):
    text = entry.get_text().strip()
    if text:
        store = entry.get_completion().get_model()
        if text not in [row[0] for row in store]:
            store.append((text, ))
        thread = threads.RecommendationsFetcher(text, self.queue)# <- 1
        self.idle_timer = gobject.idle_add(self._pulse)# <- 2
        tv_results = self.widgets.get_widget('tv_results')
        model = tv_results.get_model()
        model.clear()
        thread.setDaemon(True)# <- 3
        progress_update = self.widgets.get_widget('progress_update')
        progress_update.show()
        thread.start()# <- 4

In questo modo, l'applicazione aggiorna la GUI quando è " idle " (con GTK significa) che non causa blocchi.

  • 1: crea thread
  • 2: crea il timer di inattività
  • 3: daemonize thread in modo che l'app possa essere chiusa senza attendere il completamento del thread
  • 4: avvia discussione

Altri suggerimenti

In genere è meglio evitare discussioni quando è possibile. È molto difficile scrivere correttamente un'applicazione filettata e ancora più difficile sapere che hai capito bene. Dal momento che stai scrivendo un'applicazione GUI, è più facile per te visualizzare come farlo, dal momento che devi già scrivere l'applicazione in un framework asincrono.

La cosa importante da capire è che un'applicazione GUI non sta facendo molto. Trascorre la maggior parte del tempo in attesa che il sistema operativo gli dica che è successo qualcosa. Puoi fare molte cose in questo tempo di inattività fintanto che sai come scrivere codice di lunga durata in modo che non si blocchi.

Puoi risolvere il tuo problema originale usando un timeout; chiedendo al framework GUI di richiamare alcune funzioni dopo un ritardo, quindi reimpostare quel ritardo o avviare un'altra chiamata ritardata.

Un'altra domanda comune è come comunicare in rete in un'applicazione GUI. Le app di rete sono come le app della GUI in quanto attendono molto. L'uso di un framework IO di rete (come Twisted ) semplifica l'attesa cooperativa di entrambe le parti anziché competitiva, e di nuovo allevia la necessità di discussioni extra.

I calcoli a esecuzione prolungata possono essere scritti in modo iterativo anziché sincrono, ed è possibile eseguire l'elaborazione mentre la GUI è inattiva. Puoi usare un generatore per farlo abbastanza facilmente in Python.

def long_calculation(param, callback):
    result = None
    while True:
        result = calculate_next_part(param, result)
        if calculation_is_done(result):
            break
        else:
            yield
    callback(result)

La chiamata a long_calculation ti darà un oggetto generatore e la chiamata .next() sull'oggetto generatore eseguirà il generatore fino a quando non raggiunge yield o return. Diresti al framework GUI di chiamare long_calculation(some_param, some_callback).next quando ha tempo, e alla fine il tuo callback verrà chiamato con il risultato.

Non conosco molto bene GTK, quindi non posso dirti quali funzioni del gobject dovresti chiamare. Con questa spiegazione, tuttavia, dovresti essere in grado di trovare le funzioni necessarie nella documentazione o, nel peggiore dei casi, chiedere su un canale IRC pertinente.

Sfortunatamente non esiste una buona risposta al caso generale. Se chiarisci esattamente cosa stai cercando di fare, sarebbe più facile spiegare perché non hai bisogno di discussioni in quella situazione.

Non è possibile riavviare un oggetto thread arrestato; non provarci. Invece, crea una nuova istanza dell'oggetto se desideri riavviarlo dopo che è stato effettivamente arrestato e unito.

Ho giocato con diversi strumenti per aiutare a ripulire il lavoro con thread, elaborazione inattiva, ecc.

make_idle è un decoratore di funzioni che ti consente di eseguire un'attività in background in modo cooperativo. Questa è una buona via di mezzo tra qualcosa di abbastanza breve da eseguire una volta nel thread dell'interfaccia utente e non influire sulla reattività dell'app e fare un thread completo in una sincronizzazione speciale. All'interno della funzione decorata si usa & Quot; yield & Quot; per restituire l'elaborazione alla GUI in modo che possa rimanere reattiva e la prossima volta che l'interfaccia utente è inattiva, riprenderà nella tua funzione da dove avevi interrotto. Quindi, per iniziare, basta chiamare idle_add alla funzione decorata.

def make_idler(func):
    """
    Decorator that makes a generator-function into a function that will
continue execution on next call
    """
    a = []

    @functools.wraps(func)
    def decorated_func(*args, **kwds):
        if not a:
            a.append(func(*args, **kwds))
        try:
            a[0].next()
            return True
        except StopIteration:
            del a[:]
            return False

    return decorated_func

Se è necessario eseguire un po 'più di elaborazione, è possibile utilizzare un gestore di contesto per bloccare il thread dell'interfaccia utente ogni volta che è necessario per rendere il codice un po' più sicuro

@contextlib.contextmanager
def gtk_critical_section():
    gtk.gdk.threads_enter()
    try:
        yield
    finally:
        gtk.gdk.threads_leave()

con quello puoi semplicemente

with gtk_critical_section():
    ... processing ...

Non l'ho ancora finito, ma nel combinare le cose puramente in idle e puramente in un thread, ho un decoratore (non ancora testato quindi non pubblicato) che puoi dire se la sezione successiva dopo la resa è da eseguire nel tempo di inattività dell'interfaccia utente o in un thread. Ciò consentirebbe di eseguire alcune impostazioni nel thread dell'interfaccia utente, passare a un nuovo thread per eseguire operazioni in background e quindi passare al tempo di inattività dell'interfaccia utente per eseguire la pulizia, riducendo al minimo la necessità di blocchi.

Non ho cercato in dettaglio il tuo codice. Ma vedo due soluzioni al tuo problema:

Non usare discussioni. Utilizzare invece un timeout, come questo:

import gobject

i = 0
def do_print():
    global i
    print i
    i += 1
    if i == 10:
        main_loop.quit()
        return False
    return True

main_loop = gobject.MainLoop()
gobject.timeout_add(250, do_print)
main_loop.run()

Quando usi i thread, devi assicurarti che il tuo codice GUI sia chiamato da un solo thread contemporaneamente proteggendolo in questo modo:

import threading
import time

import gobject
import gtk

gtk.gdk.threads_init()

def run_thread():
    for i in xrange(10):
        time.sleep(0.25)
        gtk.gdk.threads_enter()
        # update the view here
        gtk.gdk.threads_leave()
    gtk.gdk.threads_enter()
    main_loop.quit()
    gtk.gdk.threads_leave()

t = threading.Thread(target=run_thread)
t.start()
main_loop = gobject.MainLoop()
main_loop.run()
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top