Estoy escribiendo una aplicación Python + GObject que necesita leer una cantidad no trivial de datos del disco al inicio. Los datos se leen sincrónicamente y se tarda unos 10 segundos en finalizar la operación de lectura, tiempo durante el cual la carga de la IU se retrasa.
Me gustaría ejecutar la tarea de forma asincrónica y recibir una notificación cuando esté lista, sin bloquear la interfaz de usuario, más o menos como:
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()
He usado GTask en el pasado para este tipo de cosas, pero me preocupa que su código no haya sido tocado en 3 años, y mucho menos haya sido portado a GObject Introspection. Lo más importante, ya no está disponible en Ubuntu 12.04. Por lo tanto, estoy buscando una manera fácil de ejecutar tareas de forma asincrónica, ya sea en una forma estándar de Python o en una forma estándar GObject / GTK +.
Editar: aquí hay un código con un ejemplo de lo que estoy tratando de hacer. He intentado python-defer
como se sugiere en los comentarios, pero no pude ejecutar la tarea larga de forma asincrónica y dejar que la interfaz de usuario se cargue sin tener que esperar a que termine. Explore el código de prueba .
¿Existe una manera fácil y ampliamente utilizada de ejecutar tareas asincrónicas y recibir notificaciones cuando hayan terminado?
fuente
async_call
función podría ser lo que necesito. ¿Te importaría expandirlo un poco y agregar una respuesta, para que pueda aceptarlo y acreditarte después de probarlo? ¡Gracias!Respuestas:
Su problema es muy común, por lo tanto, hay toneladas de soluciones (cobertizos, colas con multiprocesamiento o subprocesamiento, grupos de trabajadores, ...)
Como es tan común, también hay una solución incorporada de Python (en 3.2, pero con respaldo aquí: http://pypi.python.org/pypi/futures ) llamada concurrent.futures. Los 'futuros' están disponibles en muchos idiomas, por lo tanto, Python los llama de la misma manera. Aquí están las llamadas típicas (y aquí está su ejemplo completo , sin embargo, la parte db se reemplaza por dormir, vea a continuación por qué).
Ahora a su problema, que es mucho más complicado de lo que sugiere su simple ejemplo. En general, tiene hilos o procesos para resolver esto, pero esta es la razón por la cual su ejemplo es tan complicado:
slow_load
desde la base de datos no se pueden pickear, lo que significa que no se pueden pasar simplemente entre procesos. Entonces: ¡no multiprocesamiento con resultados de centro de software!print
, no hay cambios de estado de gtk, ¡excepto agregar una devolución de llamada!threads_init
, y si llama a un método gtk o similar, debe proteger ese método (en versiones anteriores esto eragtk.gdk.threads_enter()
,gtk.gdk.threads_leave()
ver, por ejemplo, gstreamer: http://pygstdocs.berlios.de/pygst-tutorial/playbin. html )Puedo darte la siguiente sugerencia:
slow_load
a escribir para devolver resultados de piquete y utilizar futuros con procesos.Como nota: las soluciones dadas por los otros (
Gio.io_scheduler_push_job
,async_call
) hacer trabajo contime.sleep
pero no consoftwarecenter.db
. Esto se debe a que todo se reduce a subprocesos o procesos y subprocesos para no funcionar con gtk ysoftwarecenter
.fuente
Aquí hay otra opción que usa el Programador de E / S de GIO (nunca lo he usado desde Python, pero el ejemplo a continuación parece funcionar bien).
fuente
También puede usar GLib.idle_add (devolución de llamada) para llamar a la tarea de ejecución larga una vez que GLib Mainloop finaliza todos sus eventos de mayor prioridad (que creo que incluye la construcción de la interfaz de usuario).
fuente
callback
se llama, eso se haría sincrónicamente, bloqueando así la interfaz de usuario, ¿verdad?idle_add
es que el valor de retorno de la devolución de llamada es importante. Si es cierto, se volverá a llamar.Use la
Gio
API introspectada para leer un archivo, con sus métodos asincrónicos, y al hacer la llamada inicial, hágalo como un tiempo de espera conGLib.timeout_add_seconds(3, call_the_gio_stuff)
dondecall_the_gio_stuff
hay una función que regresaFalse
.Es necesario agregar el tiempo de espera aquí (sin embargo, se puede requerir un número diferente de segundos), porque aunque las llamadas asincrónicas de Gio son asíncronas, no son sin bloqueo, lo que significa que la actividad de disco pesado de leer un archivo grande o grande número de archivos, puede provocar una IU bloqueada, ya que la IU y la E / S todavía están en el mismo hilo (principal).
Si desea escribir sus propias funciones para ser asíncrono e integrarse con el bucle principal, utilizando las API de E / S de archivos de Python, tendrá que escribir el código como un GObject, o pasar devoluciones de llamada, o usarlo
python-defer
para ayudarlo hazlo. Pero es mejor usar Gio aquí, ya que puede brindarte muchas características agradables, especialmente si estás abriendo / guardando archivos en la UX.fuente
Gio
API. Lo que me preguntaba es si hay una manera de ejecutar cualquier tarea genérica de ejecución larga de forma asincrónica de la misma manera que solía hacer GTask.Creo que vale la pena señalar que esta es una forma complicada de hacer lo que @mhall sugirió.
Esencialmente, tienes que ejecutar esto y luego ejecutar esa función de async_call.
Si quieres ver cómo funciona, puedes jugar con el temporizador de reposo y seguir haciendo clic en el botón. Es esencialmente lo mismo que la respuesta de @ mhall, excepto que hay un código de ejemplo.
Basado en esto, que no es mi trabajo.
Nota adicional, debe dejar que el otro subproceso termine antes de que finalice correctamente o busque un file.lock en su subproceso secundario.
Editar al comentario de la dirección:
Inicialmente olvidé
GObject.threads_init()
. Evidentemente, cuando se activó el botón, se inició el enhebrado por mí. Esto enmascaró el error para mí.En general, el flujo es crear la ventana en la memoria, iniciar inmediatamente el otro subproceso, cuando el subproceso completa la actualización del botón. Agregué una suspensión adicional antes de llamar a Gtk.main para verificar que la actualización completa PODRÍA ejecutarse antes de que la ventana se dibujara. También lo comenté para verificar que el inicio del hilo no impida en absoluto el dibujo de la ventana.
fuente
slow_load
que se ejecutara poco después de que se iniciara la interfaz de usuario, pero parece que nunca se llama, a menos que se haga clic en el botón, lo que me confunde un poco, ya que pensé que el propósito del botón era solo proporcionar una indicación visual. del estado de la tarea.async_call
en este ejemplo funciona para mí, pero trae caos cuando lo transfiero a mi aplicación y agrego laslow_load
función real que tengo.