Mantener las GUI receptivas durante las tareas de ejecución prolongada

Mantener la GUI receptiva mientras la aplicación realiza un procesamiento de gran cantidad de CPU es uno de los desafíos de la progtwigción efectiva de la GUI.

Aquí hay una buena discusión sobre cómo hacer esto en wxPython. Para resumir, hay 3 formas:

  1. Utilizar hilos
  2. Use wxYield
  3. Chunk el trabajo y hacerlo en el controlador de eventos IDLE

¿Qué método has encontrado para ser el más efectivo? Las técnicas de otros marcos (como Qt, GTK o API de Windows) también son bienvenidas.

Related of "Mantener las GUI receptivas durante las tareas de ejecución prolongada"

Trapos. Es lo que siempre busco porque puedes hacerlo en cualquier marco que necesites.

Y una vez que está acostumbrado a los subprocesos múltiples y al parallel processing en un idioma / marco, es bueno en todos los marcos.

Definitivamente los hilos. ¿Por qué? El futuro es multi-core. Casi cualquier nueva CPU tiene más de un núcleo o, si tiene solo uno, podría admitir el hipervínculo y, por lo tanto, fingir que tiene más de uno. Para hacer un uso eficaz de las CPU de múltiples núcleos (e Intel está planeando llegar a 32 cores en un futuro no muy lejano), necesita múltiples subprocesos. Si ejecuta todo en un subproceso principal (por lo general, el subproceso de la interfaz de usuario es el subproceso principal), los usuarios tendrán CPU con 8, 16 y un día 32 núcleos y su aplicación nunca usa más de uno de ellos, IOW se ejecuta mucho más lento de lo que podría correr.

De hecho, si planea una aplicación hoy en día, me gustaría alejarme del diseño clásico y pensar en una relación maestro / esclavo. Su interfaz de usuario es el maestro, la única tarea es interactuar con el usuario. Eso es mostrar datos al usuario y recostackr información del usuario. Siempre que su aplicación necesite “procesar cualquier información” (incluso pequeñas cantidades y otras mucho más importantes), cree una “tarea” de cualquier tipo, reenvíe esta tarea a una secuencia en segundo plano y haga que la secuencia realice la tarea, brindando retroalimentación a la Interfaz de usuario (p. Ej., Cuánto porcentaje ha completado o solo si la tarea aún se está ejecutando o no, por lo que la interfaz de usuario puede mostrar un “indicador de trabajo en curso”). Si es posible, divida la tarea en muchas subtareas pequeñas e independientes y ejecute más de un proceso en segundo plano, alimentando una subtarea a cada una de ellas. De esa manera, su aplicación puede beneficiarse realmente de los núcleos múltiples y ser más rápida cuanto más CPU tienen los núcleos.

En realidad, compañías como Apple y Microsoft ya están planeando cómo hacer que sus UIs de un solo hilo sean multihilo. Incluso con el enfoque anterior, puede que algún día tenga la situación de que la interfaz de usuario es el propio cuello de botella. Los procesos en segundo plano pueden procesar datos mucho más rápido de lo que la IU puede presentarlos al usuario o pedirle al usuario una entrada. Hoy en día, muchos marcos de IU son poco seguros para subprocesos, muchos no son seguros para subprocesos, pero eso cambiará. El procesamiento en serie (hacer una tarea después de otra) es un diseño en extinción, el procesamiento en paralelo (hacer muchas tareas a la vez) es el futuro. Basta con mirar a los adaptadores gráficos. Incluso la tarjeta NVidia más moderna tiene un rendimiento lamentable, si nos fijamos en la velocidad de procesamiento en MHz / GHz solo de la GPU. ¿Cómo puede ser mejor que la CPU fuera de los cálculos 3D? Simple: en lugar de calcular un punto de polígono o un píxel de textura tras otro, calcula muchos de ellos en paralelo (en realidad, un montón al mismo tiempo) y de esa manera alcanza un rendimiento que aún hace que las CPUs lloren. Por ejemplo, el ATI X1900 (para nombrar también al competidor) tiene 48 unidades de sombreado.

Creo que delayedresult es lo que estás buscando:

http://www.wxpython.org/docs/api/wx.lib.delayedresult-module.html

Vea la demostración de wxpython para un ejemplo.

Subprocesos o procesos según la aplicación. A veces, en realidad es mejor que la GUI sea su propio progtwig y simplemente enviar llamadas asíncronas a otros progtwigs cuando tenga trabajo por hacer. Seguirá teniendo varios subprocesos en la GUI para monitorear los resultados, pero puede simplificar las cosas si el trabajo que se realiza es complejo y no está conectado directamente a la GUI.

Subprocesos: usemos una vista simple de 2 capas (GUI, lógica de aplicación).

El trabajo de lógica de la aplicación se debe hacer en un hilo de Python separado. Para los eventos asíncronos que necesitan propagarse hasta la capa GUI, use el sistema de eventos de wx para publicar eventos personalizados. Publicar eventos wx es seguro para subprocesos por lo que posiblemente podría hacerlo desde múltiples contextos.

Trabajando en la otra dirección (eventos de entrada de GUI que activan la lógica de la aplicación), he encontrado que lo mejor es lanzar un sistema de eventos personalizado. Use el módulo Queue para tener una forma segura de subprocesos y objetos emergentes de eventos. Luego, para cada función miembro síncrona, combínela con una versión asíncrona que empuje el objeto de la función de sincronización y los parámetros en la cola de eventos.

Esto funciona particularmente bien si solo se puede realizar una sola operación a nivel de lógica a la vez. El beneficio de este modelo es que la sincronización es simple: cada función síncrona funciona dentro de su propio contexto de manera secuencial de principio a fin, sin preocuparse por la preferencia o el rendimiento codificado a mano. No necesitarás cerraduras para proteger tus secciones críticas. Al final de la función, publique un evento en la capa GUI que indique que la operación ha finalizado.

Puede escalar esto para permitir que existan múltiples subprocesos de nivel de aplicación, pero volverán a aparecer las preocupaciones habituales con la sincronización.

editar – Olvidé mencionar que lo mejor de esto es que es posible desacoplar completamente la lógica de la aplicación del código GUI. La modularidad ayuda si alguna vez decide utilizar un marco diferente o una versión de línea de comandos de la aplicación. Para hacer esto, necesitará un distribuidor de eventos intermedio (nivel de aplicación -> GUI) implementado por la capa GUI.

Trabajando con Qt / C ++ para Win32.

Dividimos las principales unidades de trabajo en diferentes procesos. La GUI se ejecuta como un proceso separado y puede ordenar / recibir datos de los procesos “de trabajo” según sea necesario. Funciona bien en el mundo multi-core de hoy.

Esta respuesta no se aplica a la pregunta del OP con respecto a Python, sino que es más bien una meta-respuesta.

La manera fácil es hilos. Sin embargo, no todas las plataformas tienen subprocesos preventivos (por ejemplo, BREW, algunos otros sistemas integrados) Si es posible, simplemente fragmenta el trabajo y hazlo en el controlador de eventos IDLE.

Otro problema con el uso de subprocesos en BREW es que no limpia los objetos de la stack de C ++, por lo que es demasiado fácil perder memoria si simplemente mata el subproceso.

Uso hilos para que el bucle de eventos principal de la GUI nunca se bloquee.

Para algunos tipos de operaciones, usar procesos separados tiene mucho sentido. De vuelta en el día, generando un proceso incurrió en una gran cantidad de gastos generales. Con el hardware moderno, esta sobrecarga apenas es un problema en la pantalla. Esto es especialmente cierto si está generando un proceso de larga ejecución.

Una ventaja (discutible) es que es un modelo conceptual más simple que los hilos que podrían conducir a un código más fácil de mantener. También puede hacer que su código sea más fácil de probar, ya que puede escribir scripts de prueba que ejercitan estos procesos externos sin tener que involucrar a la GUI. Algunos incluso podrían argumentar que es la principal ventaja.

En el caso de algún código en el que trabajé una vez, el cambio de subprocesos a procesos separados dio lugar a una reducción neta de más de 5000 líneas de código y al mismo tiempo hizo que la GUI fuera más sensible, el código más fácil de mantener y probar, al mismo tiempo que mejora El rendimiento global total.