7 Stimmen

Python-Threading-Probleme für Anfänger

Als jemand, der neu in der GUI-Entwicklung in Python (mit pyGTK) ist, habe ich gerade angefangen, etwas über Threading zu lernen. Um meine Fähigkeiten zu testen, habe ich ein einfaches kleines GTK-Interface mit einem Start/Stop-Knopf geschrieben. Das Ziel ist, dass, wenn es angeklickt wird, ein Thread startet, der schnell eine Zahl im Textfeld erhöht, während die GUI reaktionsfähig bleibt.

Die grafische Benutzeroberfläche funktioniert einwandfrei, aber ich habe Probleme mit dem Threading. Wahrscheinlich ist es ein einfaches Problem, aber mein Verstand ist für den heutigen Tag ziemlich kaputt. Unten habe ich zuerst den Trackback des Python-Interpreters eingefügt, gefolgt von dem Code. Sie können gehen zu http://drop.io/pxgr5id um sie herunterzuladen. Ich verwende bzr für die Revisionskontrolle. Wenn Sie also eine Änderung vornehmen und sie wieder ablegen möchten, übertragen Sie die Änderungen bitte. Ich füge den Code auch unter http://dpaste.com/113388/ weil es Zeilennummern haben kann, und dieses Markdown-Zeug bereitet mir Kopfschmerzen.

Aktualisierung 27. Januar, 15:52 EST: Ein leicht aktualisierter Code kann hier gefunden werden: http://drop.io/threagui/asset/thread-gui-rev3-tar-gz

Rückverfolgung

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

9voto

zgoda Punkte 12611

Threading mit PyGTK ist ein bisschen knifflig, wenn man es richtig machen will. Grundsätzlich sollte man die GUI nicht von einem anderen Thread als dem Hauptthread aus aktualisieren (übliche Einschränkung in GUI-Bibliotheken). Normalerweise wird dies in PyGTK mit einem Mechanismus von Warteschlangen-Nachrichten (für die Kommunikation zwischen Workern und GUI) gemacht, die periodisch mit der Timeout-Funktion gelesen werden. Ich habe einmal eine Präsentation auf meiner lokalen LUG zu diesem Thema gehalten, Sie können den Beispielcode für diese Präsentation unter Google Code-Repository . Schauen Sie sich an MainWindow Klasse in forms/frmmain.py , speziell für die Methode _pulse() und was in on_entry_activate() (dort wird ein Thread gestartet und der Leerlauf-Timer erstellt).

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 aktualisiert die Anwendung die GUI, wenn sie sich im "Leerlauf" befindet (mit GTK-Mitteln), was zu keinem Einfrieren führt.

  • 1: Thread erstellen
  • 2: Leerlauf-Timer erstellen
  • 3: Daemonize-Thread, damit die Anwendung geschlossen werden kann, ohne auf die Beendigung des Threads zu warten
  • 4: Faden starten

3voto

habnabit Punkte 9240

Im Allgemeinen ist es besser, Fäden zu vermeiden, wenn es möglich ist. Es ist sehr schwierig, eine Anwendung mit Threads korrekt zu schreiben, und noch schwieriger zu wissen, dass man es richtig gemacht hat. Da Sie eine GUI-Anwendung schreiben, ist es für Sie einfacher, sich vorzustellen, wie man das macht, da Sie Ihre Anwendung bereits in einem asynchronen Rahmen schreiben müssen.

Wichtig ist, dass man sich darüber im Klaren ist, dass eine GUI-Anwendung eine ganze Menge nichts tut. Sie verbringt die meiste Zeit damit, darauf zu warten, dass das Betriebssystem ihr mitteilt, dass etwas passiert ist. In dieser Leerlaufzeit kann man eine Menge machen, solange man weiß, wie man langlaufenden Code schreibt, damit er nicht blockiert.

Sie können Ihr ursprüngliches Problem lösen, indem Sie eine Zeitüberschreitung verwenden, d. h. Sie weisen Ihr GUI-Framework an, eine Funktion nach einer Verzögerung aufzurufen und diese Verzögerung dann zurückzusetzen oder einen anderen verzögerten Aufruf zu starten.

Eine weitere häufig gestellte Frage ist, wie man in einer GUI-Anwendung über das Netz kommuniziert. Netzwerkanwendungen sind wie GUI-Anwendungen insofern, als dass sie eine ganze Menge warten. Die Verwendung eines Netzwerk-IO-Frameworks (wie Verdreht ) macht es einfach, beide Teile Ihrer Anwendung kooperativ statt konkurrierend warten zu lassen, was wiederum den Bedarf an zusätzlichen Threads mindert.

Langwierige Berechnungen können iterativ statt synchron geschrieben werden, und Sie können Ihre Berechnungen durchführen, während die grafische Benutzeroberfläche inaktiv ist. Sie können einen Generator verwenden, um dies 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)

Aufruf von long_calculation gibt Ihnen ein Generatorobjekt, und der Aufruf von .next() auf das Generator-Objekt wird der Generator laufen, bis er entweder yield ou return . Sie würden dem GUI-Framework einfach sagen, dass es long_calculation(some_param, some_callback).next wenn sie Zeit hat, und schließlich wird Ihr Callback mit dem Ergebnis aufgerufen.

Ich kenne GTK nicht sehr gut, daher kann ich Ihnen nicht sagen, welche gobject-Funktionen Sie aufrufen sollten. Mit dieser Erklärung sollten Sie jedoch in der Lage sein, die notwendigen Funktionen in der Dokumentation zu finden, oder schlimmstenfalls in einem entsprechenden IRC-Kanal nachzufragen.

Leider gibt es keine allgemein gültige Antwort. Wenn Sie genau erklären, was Sie vorhaben, wäre es einfacher zu erklären, warum Sie in dieser Situation keine Threads benötigen.

1voto

Charles Duffy Punkte 255246

Sie können ein angehaltenes Thread-Objekt nicht neu starten; versuchen Sie es nicht. Erstellen Sie stattdessen eine neue Instanz des Objekts, wenn Sie es neu starten wollen, nachdem es wirklich angehalten und verbunden wurde.

0voto

Ed. Punkte 449

Ich habe mit verschiedenen Tools gespielt, um die Arbeit mit Threads, Leerlaufverarbeitung usw. zu erleichtern.

make_idle ist ein Funktionsdekorator, mit dem Sie eine Aufgabe im Hintergrund kooperativ ausführen können. Dies ist ein guter Mittelweg zwischen etwas, das kurz genug ist, um einmal im UI-Thread zu laufen und die Reaktionsfähigkeit der App nicht zu beeinträchtigen, und einem vollwertigen Thread mit spezieller Synchronisation. Innerhalb der dekorierten Funktion verwenden Sie "yield", um die Verarbeitung zurück an die GUI zu übergeben, so dass sie reaktionsfähig bleibt und das nächste Mal, wenn die UI im Leerlauf ist, wird sie in Ihrer Funktion dort weitermachen, wo Sie aufgehört haben. Um dies in Gang zu setzen, rufen Sie einfach idle_add in der dekorierten Funktion auf.

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 etwas mehr verarbeiten müssen, können Sie einen Kontextmanager verwenden, um den UI-Thread bei Bedarf zu sperren und so den Code etwas sicherer zu machen

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

Damit können Sie einfach

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

Ich bin damit noch nicht fertig, aber bei der Kombination von reinem Leerlauf und reinem Thread habe ich einen Dekorator (noch nicht getestet, daher nicht gepostet), mit dem man angeben kann, ob der nächste Abschnitt nach der Ausbeute in der Leerlaufzeit der UI oder in einem Thread ausgeführt werden soll. Dies würde es einem ermöglichen, einige Einstellungen im UI-Thread vorzunehmen, in einen neuen Thread zu wechseln, um Hintergrundaufgaben zu erledigen, und dann in die Leerlaufzeit der UI zu wechseln, um die Aufräumarbeiten durchzuführen, wodurch die Notwendigkeit von Sperren minimiert wird.

0voto

Sebastian Rittau Punkte 18863

Ich habe mir Ihren Code nicht im Detail angesehen. Aber ich sehe zwei Lösungen für Ihr Problem:

Verwenden Sie überhaupt keine Fäden. Verwenden Sie stattdessen ein Timeout, etwa so:

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 Sie Threads verwenden, müssen Sie sicherstellen, dass Ihr GUI-Code nur von einem Thread zur gleichen Zeit aufgerufen wird, indem Sie ihn wie folgt absichern:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X