Ich schreibe eine Python + GObject-App, die beim Start eine nicht unbedeutende Datenmenge von der Festplatte lesen muss. Die Daten werden synchron gelesen und es dauert ungefähr 10 Sekunden, bis der Lesevorgang abgeschlossen ist. Während dieser Zeit verzögert sich das Laden der Benutzeroberfläche.
Ich möchte die Aufgabe asynchron ausführen und eine Benachrichtigung erhalten, wenn sie fertig ist, ohne die Benutzeroberfläche zu blockieren.
def take_ages():
read_a_huge_file_from_disk()
def on_finished_long_task():
print "Finished!"
run_long_task(task=take_ages, callback=on_finished_long_task)
load_the_UI_without_blocking_on_long_task()
In der Vergangenheit habe ich GTask für diese Art von Dingen verwendet, aber ich bin besorgt, dass der Code seit drei Jahren nicht mehr geändert wurde , geschweige denn nach GObject Introspection portiert wurde. Vor allem ist es in Ubuntu 12.04 nicht mehr verfügbar. Daher suche ich nach einer einfachen Möglichkeit, Aufgaben asynchron auszuführen, entweder auf Standard-Python-Weise oder auf GObject / GTK + -Standard-Weise.
Bearbeiten: Hier ist ein Code mit einem Beispiel, was ich versuche zu tun. Ich habe versucht, python-defer
wie in den Kommentaren vorgeschlagen, aber ich konnte es nicht schaffen, die lange Aufgabe asynchron auszuführen und die Benutzeroberfläche laden zu lassen, ohne auf den Abschluss warten zu müssen. Durchsuchen Sie den Testcode .
Gibt es eine einfache und weit verbreitete Möglichkeit, asynchrone Aufgaben auszuführen und benachrichtigt zu werden, wenn sie abgeschlossen sind?
quelle
async_call
Funktion könnte das sein, was ich brauche. Würde es Ihnen etwas ausmachen, es etwas zu erweitern und eine Antwort hinzuzufügen, damit ich es akzeptieren und Ihnen gutschreiben kann, nachdem ich es getestet habe? Vielen Dank!Antworten:
Ihr Problem ist sehr verbreitet, daher gibt es unzählige Lösungen (Schuppen, Warteschlangen mit Mehrfachverarbeitung oder Threading, Worker-Pools, ...)
Da dies so häufig vorkommt, gibt es auch eine integrierte Python-Lösung (in Version 3.2, hier jedoch als Backport: http://pypi.python.org/pypi/futures ) mit dem Namen concurrent.futures. Futures sind in vielen Sprachen verfügbar, daher nennt Python sie auch so. Hier sind die typischen Anrufe (und hier ist Ihr vollständiges Beispiel , jedoch wird der DB-Teil durch Schlaf ersetzt, siehe unten, warum).
Nun zu Ihrem Problem, das viel komplizierter ist, als Ihr einfaches Beispiel nahelegt. Im Allgemeinen haben Sie Threads oder Prozesse, um dies zu lösen, aber hier ist der Grund, warum Ihr Beispiel so kompliziert ist:
slow_load
aus der Datenbank zurückgeben möchten, sind nicht auswählbar. Dies bedeutet, dass sie nicht einfach zwischen Prozessen übergeben werden können. Also: keine Mehrfachverarbeitung mit Softwarecenter-Ergebnissen!print
, keine Änderungen des GTK-Status, außer das Hinzufügen eines Rückrufs!threads_init
, und wenn Sie ein gtk oder gleich Methode aufrufen, müssen Sie diese Methode schützen (in früheren Versionen wargtk.gdk.threads_enter()
,gtk.gdk.threads_leave()
zum Beispiel gstreamer sehen. Http://pygstdocs.berlios.de/pygst-tutorial/playbin. html ).Ich kann Ihnen folgenden Vorschlag machen:
slow_load
Schreiben Sie Ihre Daten neu, um aussagekräftige Ergebnisse zu erhalten und Futures für Prozesse zu verwenden.Als Anmerkung: die von den anderen gegeben Lösungen (
Gio.io_scheduler_push_job
,async_call
) zu tun Arbeit mit ,time.sleep
aber nicht mitsoftwarecenter.db
. Dies liegt daran, dass alles auf Threads oder Prozesse und Threads hinausläuft, um nicht mit gtk und zu funktionierensoftwarecenter
.quelle
Hier ist eine weitere Option, die den I / O-Scheduler von GIO verwendet (ich habe ihn noch nie in Python verwendet, aber das folgende Beispiel scheint einwandfrei zu funktionieren).
quelle
Sie können auch GLib.idle_add (Rückruf) verwenden, um die Task mit langer Laufzeit aufzurufen, sobald der GLib-Mainloop alle Ereignisse mit höherer Priorität abgeschlossen hat (was meines Erachtens das Erstellen der Benutzeroberfläche einschließt).
quelle
callback
aufgerufen wird, dies synchron erfolgen würde, wodurch die Benutzeroberfläche blockiert würde, oder?idle_add
ist, dass der Rückgabewert des Rückrufs von Bedeutung ist. Wenn es wahr ist, wird es erneut aufgerufen.Verwenden Sie die introspected-
Gio
API, um eine Datei mit ihren asynchronen Methoden zu lesen, und tun Sie dies beim ersten Aufruf als Zeitüberschreitung,GLib.timeout_add_seconds(3, call_the_gio_stuff)
wenncall_the_gio_stuff
eine Funktion zurückgegeben wirdFalse
.Das Zeitlimit muss hier hinzugefügt werden (es kann jedoch eine andere Anzahl von Sekunden erforderlich sein), da die asynchronen Gio-Aufrufe zwar asynchron sind, aber nicht blockieren Die Anzahl der Dateien kann zu einer Blockierung der Benutzeroberfläche führen, da sich Benutzeroberfläche und E / A immer noch im selben (Haupt-) Thread befinden.
Wenn Sie Ihre eigenen Funktionen so schreiben möchten, dass sie asynchron sind und mit Pythons Datei-E / A-APIs in die Hauptschleife integriert werden, müssen Sie den Code als GObject schreiben oder Rückrufe weitergeben oder
python-defer
als Hilfe verwenden Tu es. Aber es ist am besten, Gio hier zu verwenden, da es Ihnen viele nette Funktionen bieten kann, besonders wenn Sie Dateien in der UX öffnen / speichern.quelle
Gio
API verwenden kann. Was ich mich gefragt habe, ist, ob es eine Möglichkeit gibt, eine generische Aufgabe mit langer Laufzeit asynchron auszuführen, wie GTask es früher getan hat.Ich denke, es ist erwähnenswert, dass dies eine verschlungene Methode ist, um das zu tun, was @mhall vorgeschlagen hat.
Im Wesentlichen müssen Sie dies ausführen und dann die Funktion async_call ausführen.
Wenn Sie sehen möchten, wie es funktioniert, können Sie mit dem Sleep-Timer spielen und auf die Schaltfläche klicken. Es ist im Wesentlichen dasselbe wie die Antwort von @ mhall, außer dass es Beispielcode gibt.
Darauf basierend ist das nicht meine Arbeit.
Zusätzlicher Hinweis, Sie müssen den anderen Thread beenden lassen, bevor er ordnungsgemäß beendet wird, oder nach einer Dateisperre in Ihrem untergeordneten Thread suchen.
An Adresse ändern Kommentar:
Anfangs habe ich vergessen
GObject.threads_init()
. Offensichtlich wurde das Threading für mich initialisiert, als der Knopf ausgelöst wurde. Dies maskierte den Fehler für mich.Im Allgemeinen wird das Fenster im Speicher erstellt und der andere Thread sofort gestartet, wenn der Thread die Aktualisierung der Schaltfläche abgeschlossen hat. Ich habe einen zusätzlichen Ruhezustand hinzugefügt, bevor ich Gtk.main angerufen habe, um zu überprüfen, ob das vollständige Update ausgeführt werden KANN, bevor das Fenster überhaupt gezeichnet wurde. Ich habe es auch auskommentiert, um sicherzustellen, dass der Thread-Start das Zeichnen von Fenstern überhaupt nicht behindert.
quelle
slow_load
, dass es kurz nach dem Start der Benutzeroberfläche ausgeführt wird, aber es scheint nie aufgerufen zu werden, es sei denn, die Schaltfläche ist angeklickt, was mich ein wenig verwirrt, da ich dachte, dass der Zweck der Schaltfläche nur die visuelle Anzeige ist des Zustands der Aufgabe.async_call
in diesem Beispiel funktioniert für mich, aber es bringt Chaos, wenn ich es auf meine App portiere und die eigentlicheslow_load
Funktion hinzufüge, die ich habe.