En informática , el bucle de eventos (también conocido como despachador de mensajes , bucle de mensajes , bomba de mensajes o bucle de ejecución ) es una construcción de programación o patrón de diseño que espera y envía eventos o mensajes en un programa . El bucle de eventos funciona haciendo una solicitud a un "proveedor de eventos" interno o externo (que generalmente bloquea la solicitud hasta que llega un evento) y luego llama al controlador de eventos relevante ("envía el evento").
También se implementa comúnmente en servidores como servidores web .
El bucle de eventos se puede utilizar junto con un reactor , si el proveedor de eventos sigue la interfaz de archivo, que se puede seleccionar o "sondear" (la llamada al sistema Unix, no el sondeo real ). El bucle de eventos casi siempre funciona de forma asincrónica con el originador del mensaje.
Cuando el bucle de eventos constituye la estructura central del flujo de control de un programa, como suele suceder, se lo puede denominar bucle principal o bucle de eventos principal . Este título es apropiado, porque un bucle de eventos de este tipo se encuentra en el nivel más alto de control dentro del programa.
Se dice que los bombeos de mensajes "bombean" mensajes desde la cola de mensajes del programa (asignada y generalmente propiedad del sistema operativo subyacente) al programa para su procesamiento. En el sentido más estricto, un bucle de eventos es uno de los métodos para implementar la comunicación entre procesos . De hecho, el procesamiento de mensajes existe en muchos sistemas, incluido un componente a nivel de núcleo del sistema operativo Mach . El bucle de eventos es una técnica de implementación específica de los sistemas que utilizan el paso de mensajes .
Este enfoque contrasta con varias otras alternativas:
Debido al predominio de las interfaces gráficas de usuario , la mayoría de las aplicaciones modernas cuentan con un bucle principal. La get_next_message()
rutina normalmente la proporciona el sistema operativo y se bloquea hasta que hay un mensaje disponible. Por lo tanto, el bucle solo se activa cuando hay algo que procesar.
función principal inicializar() mientras mensaje != salir mensaje := obtener_siguiente_mensaje() proceso_mensaje(mensaje) fin mientras fin función
En Unix , el paradigma de que " todo es un archivo " conduce naturalmente a un bucle de eventos basado en archivos. La lectura y escritura de archivos, la comunicación entre procesos, la comunicación en red y el control de dispositivos se logran mediante E/S de archivos, con el destino identificado por un descriptor de archivo . Las llamadas al sistema de selección y sondeo permiten monitorear un conjunto de descriptores de archivos para detectar un cambio de estado, por ejemplo, cuando los datos están disponibles para ser leídos.
Por ejemplo, considere un programa que lee desde un archivo actualizado continuamente y muestra su contenido en el X Window System , que se comunica con los clientes a través de un socket (ya sea un dominio Unix o Berkeley ):
def main (): archivo_fd = open ( "archivo_registro.log" ) x_fd = open_display () construir_interfaz () mientras True : rlist , _ , _ = select.select ([ archivo_fd , x_fd ], [], []) : si archivo_fd en rlist : datos = archivo_fd.read ( ) añadir_a_mostrar ( datos ) enviar_repintar_mensaje ( ) si x_fd en rlist : procesar_x_mensajes ( )
Una de las pocas cosas en Unix que no se ajusta a la interfaz de archivo son los eventos asincrónicos ( señales ). Las señales se reciben en manejadores de señales , pequeñas porciones limitadas de código que se ejecutan mientras el resto de la tarea está suspendida; si se recibe y maneja una señal mientras la tarea está bloqueada en select()
, select regresará temprano con EINTR ; si se recibe una señal mientras la tarea está limitada por la CPU , la tarea se suspenderá entre instrucciones hasta que regrese el manejador de señales.
Por lo tanto, una forma obvia de manejar señales es que los manejadores de señales establezcan un indicador global y hagan que el bucle de eventos verifique el indicador inmediatamente antes y después de la select()
llamada; si está establecido, manejen la señal de la misma manera que con los eventos en los descriptores de archivos. Desafortunadamente, esto da lugar a una condición de carrera : si una señal llega inmediatamente entre la verificación del indicador y la llamada select()
, no se manejará hasta que select()
regrese por alguna otra razón (por ejemplo, ser interrumpido por un usuario frustrado).
La solución a la que llegó POSIX es la pselect()
llamada, que es similar a select()
pero toma un sigmask
parámetro adicional, que describe una máscara de señal . Esto permite que una aplicación enmascare las señales en la tarea principal y luego elimine la máscara durante la select()
llamada, de modo que los controladores de señales solo se llamen mientras la aplicación esté limitada por E/S . Sin embargo, las implementaciones de pselect()
no siempre han sido confiables; las versiones de Linux anteriores a 2.6.16 no tienen una pselect()
llamada al sistema, [1] lo que se pretende evitar es forzar a glibc a emularla a través de un método propenso a la misma condición de carrera .pselect()
Una solución alternativa, más portable, es convertir eventos asincrónicos a eventos basados en archivos usando el truco de la auto-tubería , [2] donde "un manejador de señales escribe un byte en una tubería cuyo otro extremo es monitoreado por select()
el programa principal". [3] En la versión 2.6.22 del kernel de Linux , se agregó una nueva llamada al sistema signalfd()
, que permite recibir señales a través de un descriptor de archivo especial.
Una página web y su código JavaScript normalmente se ejecutan en un proceso de navegador web de un solo subproceso . El proceso del navegador se ocupa de los mensajes de una cola de uno en uno. Una función de JavaScript u otro evento del navegador pueden estar asociados a un mensaje determinado. Cuando el proceso del navegador ha terminado con un mensaje, pasa al siguiente mensaje de la cola.
En el sistema operativo Microsoft Windows , un proceso que interactúa con el usuario debe aceptar y reaccionar a los mensajes entrantes, lo que se realiza casi inevitablemente mediante un bucle de mensajes en ese proceso. En Windows, un mensaje se equipara a un evento creado e impuesto al sistema operativo. Un evento puede ser la interacción del usuario, el tráfico de red, el procesamiento del sistema, la actividad del temporizador, la comunicación entre procesos, entre otros. Para los eventos no interactivos, solo de E/S, Windows tiene puertos de finalización de E/S . Los bucles de puertos de finalización de E/S se ejecutan por separado del bucle de mensajes y no interactúan con este de forma predeterminada.
El "corazón" de la mayoría de las aplicaciones Win32 es la función WinMain(), que llama a GetMessage() en un bucle. GetMessage() se bloquea hasta que se recibe un mensaje o "evento" (con la función PeekMessage() como una alternativa no bloqueante). Después de un procesamiento opcional, llamará a DispatchMessage(), que envía el mensaje al controlador correspondiente, también conocido como WindowProc . Normalmente, los mensajes que no tienen un WindowProc() especial se envían a DefWindowProc , el predeterminado. DispatchMessage() llama al WindowProc del controlador HWND del mensaje (registrado con la función RegisterClass()).
Las versiones más recientes de Microsoft Windows garantizan al programador que los mensajes se entregarán al bucle de mensajes de una aplicación en el orden en que fueron percibidos por el sistema y sus periféricos. Esta garantía es esencial si se tienen en cuenta las consecuencias de diseño de las aplicaciones multiproceso .
Sin embargo, algunos mensajes tienen reglas diferentes, como los mensajes que siempre se reciben últimos o los mensajes con una prioridad documentada diferente. [4]
Las aplicaciones X que utilizan Xlib directamente se construyen en torno a la XNextEvent
familia de funciones; XNextEvent
se bloquea hasta que aparece un evento en la cola de eventos, momento en el que la aplicación lo procesa de forma adecuada. El bucle de eventos de Xlib solo maneja eventos del sistema de ventanas; las aplicaciones que necesitan poder esperar otros archivos y dispositivos podrían construir su propio bucle de eventos a partir de primitivas como ConnectionNumber
, pero en la práctica tienden a utilizar subprocesos múltiples .
Muy pocos programas utilizan Xlib directamente. En el caso más común, los kits de herramientas de GUI basados en Xlib suelen admitir la adición de eventos. Por ejemplo, los kits de herramientas basados en Xt Intrinsics tienen XtAppAddInput()
y XtAppAddTimeout()
.
Tenga en cuenta que no es seguro llamar a funciones Xlib desde un controlador de señales, ya que la aplicación X puede haberse interrumpido en un estado arbitrario, por ejemplo, dentro de XNextEvent
. Consulte [1] para obtener una solución para X11R5, X11R6 y Xt.
El bucle de eventos de GLib se creó originalmente para su uso en GTK , pero ahora también se utiliza en aplicaciones que no son GUI, como D-Bus . El recurso consultado es la colección de descriptores de archivos en los que está interesada la aplicación; el bloque de consulta se interrumpirá si llega una señal o si se agota un tiempo de espera (por ejemplo, si la aplicación ha especificado un tiempo de espera o una tarea inactiva). Si bien GLib tiene soporte integrado para descriptores de archivos y eventos de terminación secundarios, es posible agregar una fuente de eventos para cualquier evento que pueda manejarse en un modelo de preparación-verificación-envío.[2]
Las bibliotecas de aplicaciones que se basan en el bucle de eventos de GLib incluyen GStreamer y los métodos de E/S asíncronos de GnomeVFS , pero GTK sigue siendo la biblioteca cliente más visible. Los eventos del sistema de ventanas (en X , leídos desde el socket X ) son traducidos por GDK en eventos GTK y emitidos como señales GLib en los objetos de widget de la aplicación.
Se permite exactamente un CFRunLoop por subproceso y se pueden adjuntar tantas fuentes y observadores como se desee. Las fuentes se comunican con los observadores a través del bucle de ejecución, que organiza la puesta en cola y el envío de mensajes.
El CFRunLoop se abstrae en Cocoa como un NSRunLoop, lo que permite que cualquier mensaje (equivalente a una llamada de función en tiempos de ejecución no reflexivos ) se ponga en cola para su envío a cualquier objeto.