J'écris une application Python + GObject qui doit lire une quantité non triviale de données du disque au démarrage. Les données sont lues de manière synchrone et il faut environ 10 secondes pour terminer l'opération de lecture, période pendant laquelle le chargement de l'interface utilisateur est retardé.
Je voudrais exécuter la tâche de manière asynchrone et recevoir une notification lorsqu'elle est prête, sans bloquer l'interface utilisateur, plus ou moins comme:
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()
J'ai utilisé GTask dans le passé pour ce genre de chose, mais je crains que son code n'ait pas été touché depuis 3 ans, et encore moins porté sur GObject Introspection. Plus important encore, il n'est plus disponible dans Ubuntu 12.04. Je recherche donc un moyen facile d'exécuter des tâches de manière asynchrone, soit de manière Python standard, soit de manière standard GObject / GTK +.
Edit: voici du code avec un exemple de ce que j'essaie de faire. J'ai essayé python-defer
comme suggéré dans les commentaires, mais je n'ai pas réussi à exécuter la tâche longue de manière asynchrone et à laisser l'interface utilisateur se charger sans avoir à attendre qu'elle se termine. Parcourez le code de test .
Existe-t-il un moyen simple et largement utilisé d'exécuter des tâches asynchrones et d'être averti lorsqu'elles sont terminées?
la source
async_call
fonction pourrait être ce dont j'ai besoin. Pourriez-vous m'étendre un peu et ajouter une réponse, afin que je puisse l'accepter et vous créditer après l'avoir testée? Merci!Réponses:
Votre problème est très courant, donc il y a des tonnes de solutions (hangars, files d'attente avec multiprocessing ou threading, pools de travailleurs, ...)
Comme il est si courant, il existe également une solution intégrée de python (en 3.2, mais rétroportée ici: http://pypi.python.org/pypi/futures ) appelée concurrent.futures. Les 'Futures' sont disponibles dans de nombreuses langues, donc python les appelle de la même manière. Voici les appels typiques (et voici votre exemple complet , cependant, la partie db est remplacée par sleep, voir ci-dessous pourquoi).
Passons maintenant à votre problème, qui est beaucoup plus compliqué que ne le suggère votre simple exemple. En général, vous avez des threads ou des processus pour résoudre ce problème, mais voici pourquoi votre exemple est si compliqué:
slow_load
de la base de données ne sont pas sélectionnables, ce qui signifie qu'ils ne peuvent pas simplement être passés entre les processus. Donc: pas de multitraitement avec des résultats de softwarecenter!print
, pas de changement d'état gtk, sauf l'ajout d'un rappel!threads_init
, et si vous appelez une méthode gtk ou similaire, vous devez protéger cette méthode (dans les versions antérieures, c'étaitgtk.gdk.threads_enter()
,gtk.gdk.threads_leave()
voir par exemple gstreamer: http://pygstdocs.berlios.de/pygst-tutorial/playbin. html ).Je peux vous faire la suggestion suivante:
slow_load
pour retourner des résultats sélectionnables et utiliser des contrats à terme avec des processus.Comme une note: les solutions données par les autres (
Gio.io_scheduler_push_job
,async_call
) faire travailler avec ,time.sleep
mais pas avecsoftwarecenter.db
. C'est parce que tout se résume à des threads ou des processus et des threads pour ne pas fonctionner avec gtk etsoftwarecenter
.la source
Voici une autre option utilisant le planificateur d'E / S de GIO (je ne l'ai jamais utilisé auparavant depuis Python, mais l'exemple ci-dessous semble fonctionner correctement).
la source
Vous pouvez également utiliser GLib.idle_add (rappel) pour appeler la tâche de longue durée une fois que le GLib Mainloop termine tous ses événements de priorité plus élevée (ce qui, je crois, inclut la construction de l'interface utilisateur).
la source
callback
est appelé, cela se ferait de manière synchrone, bloquant ainsi l'interface utilisateur, non?idle_add
est que la valeur de retour du rappel est importante. Si c'est vrai, il sera rappelé.Utilisez l'
Gio
API introspected pour lire un fichier, avec ses méthodes asynchrones, et lors de l'appel initial, faites-le comme un timeout avecGLib.timeout_add_seconds(3, call_the_gio_stuff)
oùcall_the_gio_stuff
est une fonction qui retourneFalse
.Le délai d'attente ici est nécessaire pour ajouter (un nombre différent de secondes peut être requis, cependant), car bien que les appels asynchrones de Gio soient asynchrones, ils ne sont pas bloquants, ce qui signifie que l'activité lourde du disque de lecture d'un fichier volumineux ou volumineux nombre de fichiers, peut entraîner une interface utilisateur bloquée, car l'interface utilisateur et les E / S sont toujours dans le même thread (principal).
Si vous souhaitez écrire vos propres fonctions pour être asynchrones et les intégrer à la boucle principale, à l'aide des API d'E / S de fichiers de Python, vous devrez écrire le code en tant que GObject, ou pour passer des rappels, ou utiliser
python-defer
pour vous aider fais le. Mais il est préférable d'utiliser Gio ici, car il peut vous apporter de nombreuses fonctionnalités intéressantes, surtout si vous faites des choses d'ouverture / d'enregistrement de fichiers dans l'UX.la source
Gio
API. Ce que je me demandais, c'est s'il existe un moyen d'exécuter une tâche générique de longue durée de manière asynchrone, comme GTask le faisait auparavant.Je pense qu'il convient de noter que c'est une façon compliquée de faire ce que @mhall a suggéré.
Essentiellement, vous avez un run this, puis exécutez cette fonction de async_call.
Si vous voulez voir comment cela fonctionne, vous pouvez jouer avec la minuterie de sommeil et continuer à cliquer sur le bouton. C'est essentiellement la même chose que la réponse de @ mhall, sauf qu'il y a un exemple de code.
Sur cette base, ce n'est pas mon travail.
Remarque supplémentaire, vous devez laisser l'autre thread se terminer avant qu'il ne se termine correctement ou rechercher un fichier.lock dans votre thread enfant.
Modifier pour répondre au commentaire: au
départ, j'ai oublié
GObject.threads_init()
. Évidemment, lorsque le bouton a tiré, il a initialisé le filetage pour moi. Cela a masqué l'erreur pour moi.Généralement, le flux consiste à créer la fenêtre en mémoire, à lancer immédiatement l'autre thread, lorsque le thread se termine, mettez à jour le bouton. J'ai ajouté un sommeil supplémentaire avant même d'appeler Gtk.main pour vérifier que la mise à jour complète POURRAIT s'exécuter avant même que la fenêtre ne soit dessinée. Je l'ai également commenté pour vérifier que le lancement du fil ne gêne pas du tout le dessin de la fenêtre.
la source
slow_load
à être exécuté peu de temps après le début de l'interface utilisateur, mais il ne semble jamais être appelé, à moins que le bouton ne soit cliqué, ce qui m'embrouille un peu, car je pensais que le but du bouton était simplement de fournir une indication visuelle de l'état de la tâche.async_call
dans cet exemple fonctionne pour moi, mais cela apporte le chaos quand je le porte à mon application et j'ajoute la vraieslow_load
fonction que j'ai.