Anfänger-Ebene Python threading Probleme
-
20-08-2019 - |
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()
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()