2009-03-18 10 views
48

Si ha trabajado con kits de herramientas GUI, sabrá que hay un bucle de evento/bucle principal que se debe ejecutar después de que se haya completado todo, y que mantendrá la aplicación viva y receptiva a diferentes eventos. Por ejemplo, para Qt, podría hacer esto en main():¿Cómo implementaría un bucle de evento básico?

int main() { 
    QApplication app(argc, argv); 
    // init code 
    return app.exec(); 
} 

que en este caso, app.exec() es el principal de circuito de la aplicación.

La manera obvia de implementar este tipo de bucle sería:

void exec() { 
    while (1) { 
     process_events(); // create a thread for each new event (possibly?) 
    } 
} 

Pero esto limita la CPU al 100% y es practicamente inútil. Ahora, ¿cómo puedo implementar un ciclo de eventos que responda sin consumir la CPU por completo?

Las respuestas son apreciadas en Python y/o C++. Gracias.

Nota al pie: Por el bien de aprender, implementaré mis propias señales/ranuras, y las usaría para generar eventos personalizados (por ejemplo, go_forward_event(steps)). Pero si sabe cómo puedo usar los eventos del sistema manualmente, me gustaría saberlo también.

+3

Creo que se puede profundizar en el código fuente de Qt y ver exactamente lo que están haciendo en exec(). Eso probablemente te dará algunos buenos consejos. – JimDaniel

Respuesta

62

¡Solía ​​preguntarme mucho sobre lo mismo!

Un bucle principal de la GUI se parece a esto, en pseudo-código:

void App::exec() { 
    for(;;) { 
     vector<Waitable> waitables; 
     waitables.push_back(m_networkSocket); 
     waitables.push_back(m_xConnection); 
     waitables.push_back(m_globalTimer); 
     Waitable* whatHappened = System::waitOnAll(waitables); 
     switch(whatHappened) { 
      case &m_networkSocket: readAndDispatchNetworkEvent(); break; 
      case &m_xConnection: readAndDispatchGuiEvent(); break; 
      case &m_globalTimer: readAndDispatchTimerEvent(); break; 
     } 
    } 
} 

¿Qué es un "temporizador de espera"? Bueno, depende del sistema. En UNIX se llama "descriptor de archivo" y "waitOnAll" es la llamada :: select al sistema. El llamado vector<Waitable> es un ::fd_set en UNIX, y "whatHappened" se ha consultado realmente a través de FD_ISSET. Los identificadores reales se adquieren de varias formas; por ejemplo, m_xConnection se pueden tomar de :: XConnectionNumber(). X11 también proporciona una API portátil de alto nivel para este :: :: XNextEvent() - pero si tuviera que usar eso, no podría esperar varias fuentes de eventos simultáneamente.

¿Cómo funciona el bloqueo? "waitOnAll" es un syscall que le dice al sistema operativo que ponga su proceso en una "lista de espera". Esto significa que no tiene tiempo de CPU hasta que ocurra un evento en uno de los waitables. Esto, entonces, significa que su proceso está inactivo, consumiendo 0% de CPU. Cuando ocurre un evento, su proceso reaccionará brevemente ante él y luego volverá al estado inactivo.Las aplicaciones GUI pasan casi todos su tiempo de ralentí.

¿Qué ocurre con todos los ciclos de la CPU mientras duerme? Depende A veces, otro proceso les servirá. De lo contrario, su sistema operativo ocupará la CPU, o la pondrá en modo temporal de bajo consumo, etc.

¡Solicite más detalles!

+0

¿Cómo implementaría tal sistema de espera para esperar no las señales del sistema, sino mis propias señales? – fengshaun

+0

Como dije, su código solo se ejecuta en reacción a los eventos. Por lo tanto, si activa su propio evento, lo hará como reacción a algún evento del sistema. Y luego queda claro que realmente no necesita un sistema de eventos para sus eventos personalizados. ¡Simplemente llame a los controladores directamente! –

+0

Por ejemplo, considere una señal "Botón :: clickeado". Solo se activará en respuesta a un evento del sistema (liberación del botón izquierdo del mouse). Así que su código se convierte en "virtual void Button :: handleLeftRelease (Point) {clicked.invoke();}" sin necesidad de hilos o una cola de eventos o algo así. –

11

En general, me gustaría hacer esto con algún tipo de counting semaphore:

  1. semáforo comienza en cero.
  2. El bucle de eventos espera en el semáforo.
  3. Evento (s) entra, el semáforo se incrementa.
  4. El controlador de eventos desbloquea y disminuye el semáforo y procesa el evento.
  5. Cuando se procesan todos los eventos, el semáforo es cero y el bucle de eventos bloquea nuevamente.

Si no quieres complicarte tanto, puedes simplemente agregar una llamada sleep() en tu ciclo while con un tiempo de espera trivialmente pequeño. Eso hará que el hilo de procesamiento de mensajes ceda su tiempo de CPU a otros hilos. La CPU no se vinculará al 100% más, pero sigue siendo un desperdicio.

+0

Esto suena tentador, tendré que aprender más acerca de enhebrar. Gracias. – fengshaun

+0

, entonces necesitamos otro hilo que no tenga que esperar? –

+0

@FallingFromBed: no es una espera ocupada, sino una espera de bloqueo en un sepmaphore. La diferencia es importante porque una espera de bloqueo no consumirá tiempo de CPU. –

10

Utilizaría una biblioteca de mensajes simple y liviana llamada ZeroMQ (http://www.zeromq.org/). Es una biblioteca de código abierto (LGPL). Esta es una biblioteca muy pequeña; en mi servidor, todo el proyecto se compila en aproximadamente 60 segundos.

ZeroMQ simplificará en gran medida su código basado en eventos, Y también es la solución más eficiente en términos de rendimiento. La comunicación entre subprocesos con ZeroMQ es mucho más rápida (en términos de velocidad) que el uso de semáforos o sockets UNIX locales. ZeroMQ también es una solución 100% portátil, mientras que todas las otras soluciones vincularán su código a un sistema operativo específico.

+3

Guau, eso es genial. –

21

Python:

Usted puede mirar en la aplicación de la Twisted reactor que es probablemente la mejor aplicación para un ciclo de eventos en Python. Los reactores en Twisted son implementaciones de una interfaz y puede especificar un reactor de tipo para ejecutar: select, epoll, kqueue (todo basado en una c api usando esas llamadas al sistema), también hay reactores basados ​​en los kits de herramientas QT y GTK.

Una aplicación sencilla sería utilizar selecto:

#echo server that accepts multiple client connections without forking threads 

import select 
import socket 
import sys 

host = '' 
port = 50000 
backlog = 5 
size = 1024 
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
server.bind((host,port)) 
server.listen(backlog) 
input = [server,sys.stdin] 
running = 1 

#the eventloop running 
while running: 
    inputready,outputready,exceptready = select.select(input,[],[]) 

    for s in inputready: 

     if s == server: 
      # handle the server socket 
      client, address = server.accept() 
      input.append(client) 

     elif s == sys.stdin: 
      # handle standard input 
      junk = sys.stdin.readline() 
      running = 0 

     else: 
      # handle all other sockets 
      data = s.recv(size) 
      if data: 
       s.send(data) 
      else: 
       s.close() 
       input.remove(s) 
server.close() 
Cuestiones relacionadas