Frage

Als jemand neu in GUI-Entwicklung in Python (mit pyGTK), habe ich das Lernen über Threading gerade erst begonnen. Um meine Fähigkeiten zu testen, habe ich eine einfache kleine GTK-Schnittstelle mit einem Start / Stopp-Taste geschrieben. Das Ziel ist, dass, wenn es angeklickt wird, ein Thread beginnt, dass schnell eine Zahl in dem Textfeld erhöht, während die GUI ansprechbar.

Ich habe die GUI habe ganz gut funktioniert, aber mit dem threading Problem habe. Es ist wahrscheinlich ein einfaches Problem, aber mein Geist ist etwa für den Tag gebraten. Unten habe ich eingefügt durch den Code gefolgt zunächst die Trackback von dem Python-Interpreter. Sie können http://drop.io/pxgr5id es zum Download bereit. Ich verwende BZR für Revisionskontrolle, so dass, wenn Sie eine Änderung vornehmen möchten und es wieder fallen, bitte die Änderungen. Ich bin Einfügen den Code auch unter http://dpaste.com/113388/ weil es Zeilennummern haben kann und diese Abschlag Sachen mir Kopfschmerzen geben.

Update 27. Januar 15.52 Uhr EST: Leicht aktualisierte Code finden Sie hier: http://drop.io/threagui / Asset / 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

Code

#!/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()
War es hilfreich?

Lösung

Threading mit PyGTK ist etwas schwierig, wenn man es richtig machen will. Grundsätzlich sollten Sie nicht GUI aktualisieren können in allen anderen Thread als Haupt-Thread (gemeinsame Einschränkung in GUI-Bibliotheken). Verwendung Mechanismus der Nachrichten in der Warteschlange in der Regel wird dies in PyGTK erfolgen (für die Kommunikation zwischen den Arbeitern und GUI), die periodisch Timeout-Funktion gelesen werden. Einmal habe ich zu diesem Thema eine Präsentation auf meinem lokalen LUG hatten, können Sie Beispielcode für diese Präsentation von Repository . Werfen Sie einen Blick auf MainWindow Klasse in forms/frmmain.py, speziell für Verfahren _pulse() und was in on_entry_activate() getan wird (Thread gestartet wird es plus die Leerlaufzeitgeber erstellt wird).

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

Auf diese Weise, Anwendungs-Updates GUI, wenn ist "idle" (von GTK bedeutet) verursacht nicht friert.

  • 1: erstellen Faden
  • 2: erstellen Idletimers
  • 3: daemonize Faden, so dass die App ohne Warten auf Thread Abschluss geschlossen werden
  • 4: Start Gewinde

Andere Tipps

Im Allgemeinen ist es besser, Themen zu vermeiden, wenn Sie können. Es ist sehr schwierig, eine Thread-Anwendung korrekt zu schreiben, und noch schwieriger zu wissen, dass Sie es richtig gemacht haben. Da Sie eine GUI-Anwendung schreiben, ist es einfacher für Sie sichtbar zu machen, wie dies zu tun, da Sie bereits Ihre Anwendung in einem asynchronen Rahmen haben zu schreiben.

Die wichtige Sache zu erkennen ist, dass eine GUI-Anwendung eine ganze Reihe von nichts tut. Es verbringt die meiste Zeit für das OS wartet es zu sagen, dass etwas passiert ist. Sie können so lange in dieser Leerlaufzeit eine Menge Sachen tun, wie Sie wissen, wie lang laufenden Code zu schreiben, so dass es nicht blockiert.

Sie können Ihr ursprüngliches Problem durch die Verwendung eines Timeout lösen; erzählen Sie Ihren GUI-Framework eine Funktion nach einer Verzögerung zurückzurufen, und dann diese Verzögerung zurückzusetzen oder einen anderen verzögerten Anruf zu starten.

Eine weitere häufige Frage ist, wie über das Netzwerk in einer GUI-Anwendung zu kommunizieren. Netzwerk-Anwendungen sind wie GUI in apps, dass sie eine ganze Menge Warte tun. Mit einem Netzwerk IO Rahmen (wie Verdrehte ) macht es leicht, die beiden Teile der Anwendung zu haben, kooperativ anstatt warten zu wettbewerbsfähigen, und wieder verringert die Notwendigkeit für zusätzliche Threads.

Langlauf Berechnungen iterativ statt synchron geschrieben werden, und Sie können Ihre Verarbeitung tun, während die GUI im Leerlauf ist. Sie können einen Generator verwenden diese ganz einfach in Python zu tun.

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)

long_calculation Aufruf erhalten Sie einen Generator Objekt geben, und am Generator Objekt .next() Aufruf wird den Generator laufen, bis er entweder yield oder return erreicht. Sie würden nur den GUI-Framework sagen long_calculation(some_param, some_callback).next zu nennen, wenn es Zeit ist, und schließlich wird die Callback mit dem Ergebnis aufgerufen werden.

Ich weiß, dass GTK nicht sehr gut, so kann ich Ihnen nicht sagen, welche Funktionen gobject Sie fordern werden sollen. Mit dieser Erklärung, obwohl, sollten Sie in die Lage, die notwendigen Funktionen in der Dokumentation zu finden, oder im schlimmsten Fall, fragt auf einem entsprechenden IRC-Kanal.

Leider gibt es keine gute allgemeinen Fall Antwort. Wenn Sie mit genau klären, was Sie versuchen zu tun, wäre es einfacher, zu erklären, warum Sie Fäden nicht in dieser Situation brauchen.

Sie können nicht ein gestoppten Thread-Objekt neu zu starten; versuchen Sie nicht. Stattdessen eine neue Instanz des Objekts erstellen, wenn Sie es neu gestartet werden soll, nachdem es wirklich gestoppt ist und verbunden.

Ich habe mit verschiedenen Werkzeugen gespielt, um die Arbeit mit einem Gewinde, Leerlauf Verarbeitung aufzuräumen, etc.

make_idle ist eine Funktion, Dekorateur, die Sie gemeinsam eine Aufgabe im Hintergrund laufen zu lassen. Dies ist ein guter Mittelweg zwischen etwas kurz genug, um einmal in dem UI-Thread und kein Einfluss auf das Ansprechverhalten der App und dabei einen vollständigen out-Thread in spezieller Synchronisation auszuführen. Innerhalb der dekorierten Funktion „yield“ Sie die Verarbeitung zurück zu der GUI zur Hand, so dass es die UI ist im Leerlauf bleiben kann, wird es in Ihrer Funktion aufheben anspricht und das nächste Mal, wo Sie aufgehört hat. So bekommen diese begann man nur idle_add auf der dekorierten Funktion aufrufen.

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

Wenn Sie ein bisschen mehr Verarbeitung tun, dann können Sie einen Kontext-Manager verwenden, um den UI-Thread zu sperren, wenn nötig, um den Code ein bisschen sicherer zu machen

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

mit, dass man nur

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

Ich habe mit ihm noch nicht fertig, aber Dinge zu tun, rein in Leerlauf- und rein in einem Thread in Kombination habe ich einen Dekorateur (noch nicht so nicht geschrieben getestet), dass Sie ihm, ob der nächste Abschnitt nach dem Ertrag sagen können, ist, in der Benutzeroberfläche der Leerlaufzeit oder in einem Thread ausgeführt werden sollte. Dies würde es ermöglicht eine gewisse Einrichtung im UI-Thread zu tun, zu einem neuen Thread wechselt für den Hintergrund Sachen zu tun, und dann in die Leerlaufzeit der Benutzeroberfläche wechselt Bereinigung zu tun, was die Notwendigkeit für Sperren zu minimieren.

Ich habe nicht im Detail auf Ihrem Code aussieht. Aber ich sehe zwei Lösungen für Ihr Problem:

Sie Threads nicht verwenden. Statt einen Timeout verwenden, wie folgt aus:

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

Wenn Threads verwenden, müssen Sie sicherstellen, dass Ihr GUI-Code nur von einem Thread zur gleichen Zeit durch die so Bewachung genannt wird:

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()
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top