Ciclo di eventi - Event loop

In informatica , il ciclo di eventi è un costrutto di programmazione o un modello di progettazione che attende e invia eventi o messaggi in un programma . Il ciclo degli eventi funziona effettuando una richiesta a un "provider di eventi" interno o esterno (che generalmente blocca la richiesta fino all'arrivo di un evento), quindi chiama il relativo gestore di eventi ("invia l'evento"). Il ciclo di eventi è anche talvolta indicato come il dispatcher messaggio , ciclo di messaggi , message pump , o ciclo di esecuzione .

L'event-loop può essere utilizzato in combinazione con un reattore , se il provider di eventi segue l' interfaccia del file , che può essere selezionata o 'interrogata' (la chiamata di sistema Unix, non il polling effettivo ). Il ciclo di eventi funziona quasi sempre in modo asincrono con il mittente del messaggio.

Quando il ciclo degli eventi forma il costrutto del flusso di controllo centrale di un programma, come spesso accade, può essere definito il ciclo principale o il ciclo di eventi principale . Questo titolo è appropriato, perché un tale ciclo di eventi è al livello più alto di controllo all'interno del programma.

Messaggio che passa

Si dice che le pompe di messaggi "pompano" i messaggi dalla coda dei messaggi del programma (assegnata e di solito di proprietà del sistema operativo sottostante) nel programma per l'elaborazione. In senso stretto, un ciclo di eventi è uno dei metodi per implementare la comunicazione tra processi . In effetti, l'elaborazione dei messaggi esiste in molti sistemi, incluso un componente a livello di kernel del sistema operativo Mach . L'event loop è una tecnica di implementazione specifica dei sistemi che utilizzano lo scambio di messaggi .

Disegni alternativi

Questo approccio è in contrasto con una serie di altre alternative:

  • Tradizionalmente, un programma veniva semplicemente eseguito una volta, quindi terminato. Questo tipo di programma era molto comune agli albori dell'informatica e mancava di qualsiasi forma di interattività con l'utente. Questo è ancora usato frequentemente, in particolare sotto forma di programmi a riga di comando . Tutti i parametri vengono impostati in anticipo e passati in una volta sola all'avvio del programma.
  • Design guidati da menu. Questi possono ancora presentare un ciclo principale, ma di solito non sono pensati come guidati da eventi nel senso comune. Invece, all'utente viene presentato un insieme sempre più ristretto di opzioni fino a quando l'attività che desidera svolgere è l'unica opzione disponibile. È disponibile un'interattività limitata attraverso i menu.

Utilizzo

A causa della predominanza delle interfacce utente grafiche , la maggior parte delle applicazioni moderne presenta un ciclo principale. La get_next_message()routine è in genere fornita dal sistema operativo e si blocca finché non è disponibile un messaggio. Pertanto, il ciclo viene inserito solo quando c'è qualcosa da elaborare.

function main
    initialize()
    while message != quit
        message := get_next_message()
        process_message(message)
    end while
end function

Interfaccia file

Sotto Unix , il paradigma " tutto è un file " porta naturalmente a un ciclo di eventi basato su file. La lettura e la scrittura su file, la comunicazione tra processi, la comunicazione di rete e il controllo del dispositivo vengono tutti ottenuti utilizzando l'I/O di file, con l'obiettivo identificato da un descrittore di file . Le chiamate di sistema select e poll consentono di monitorare un insieme di descrittori di file per un cambiamento di stato, ad esempio quando i dati diventano disponibili per la lettura.

Ad esempio, si consideri un programma che legge da un file continuamente aggiornato e ne visualizza il contenuto nell'X Window System , che comunica con i client tramite un socket ( dominio Unix o Berkeley ):

def main():
    file_fd = open("logfile.log")
    x_fd = open_display()
    construct_interface()
    while True:
        rlist, _, _ = select.select([file_fd, x_fd], [], []):
        if file_fd in rlist:
            data = file_fd.read()
            append_to_display(data)
            send_repaint_message()
        if x_fd in rlist:
            process_x_messages()

Gestione dei segnali

Una delle poche cose in Unix che non è conforme all'interfaccia del file sono gli eventi asincroni ( segnali ). I segnali vengono ricevuti in gestori di segnale , piccoli pezzi di codice limitati che vengono eseguiti mentre il resto dell'attività è sospeso; se un segnale viene ricevuto e gestito mentre il task si sta bloccando select(), select tornerà presto con EINTR ; se viene ricevuto un segnale mentre il task è legato alla CPU , il task verrà sospeso tra le istruzioni fino al ritorno del gestore del segnale.

Quindi un modo ovvio per gestire i segnali è che i gestori di segnali impostino un flag globale e facciano in modo che l'event loop controlli il flag immediatamente prima e dopo la select()chiamata; se è impostato, gestisci il segnale allo stesso modo degli eventi sui descrittori di file. Sfortunatamente, questo dà luogo a una race condition : se un segnale arriva immediatamente tra il controllo del flag e la chiamata select(), non verrà gestito fino al select()ritorno per qualche altro motivo (ad esempio, essere interrotto da un utente frustrato).

La soluzione a cui è arrivato POSIX è la pselect()chiamata, che è simile select()ma richiede un sigmaskparametro aggiuntivo , che descrive una maschera di segnale . Ciò consente a un'applicazione di mascherare i segnali nell'attività principale, quindi rimuovere la maschera per la durata della select()chiamata in modo tale che i gestori dei segnali vengano chiamati solo mentre l'applicazione è associata a I/O . Tuttavia, le implementazioni di pselect()non sono sempre state affidabili; le versioni di Linux precedenti alla 2.6.16 non hanno una pselect()chiamata di sistema, costringendo glibc a emularla tramite un metodo incline alla stessa race condition che pselect()si intende evitare.

Una soluzione alternativa, più portabile, è convertire eventi asincroni in eventi basati su file utilizzando il trucco dell'auto-pipe , in cui "un gestore di segnale scrive un byte su una pipe la cui altra estremità è monitorata select()nel programma principale". Nella versione del kernel Linux 2.6.22 è signalfd()stata aggiunta una nuova chiamata di sistema , che consente di ricevere segnali tramite uno speciale descrittore di file.

implementazioni

Applicazioni Windows

Nel sistema operativo Microsoft Windows , un processo che interagisce con l'utente deve accettare e reagire ai messaggi in arrivo, operazione quasi inevitabilmente eseguita da un loop di messaggi in quel processo. In Windows, un messaggio è equiparato a un evento creato e imposto al sistema operativo. Un evento può essere l'interazione dell'utente, il traffico di rete, l'elaborazione del sistema, l'attività del timer, la comunicazione tra processi, tra gli altri. Per gli eventi non interattivi, solo I/O, Windows dispone di porte di completamento I/O . I loop delle porte di completamento I/O vengono eseguiti separatamente dal loop dei messaggi e non interagiscono con il loop dei messaggi fuori dalla scatola.

Il "cuore" della maggior parte delle applicazioni Win32 è la funzione WinMain() , che chiama GetMessage() in un ciclo. GetMessage() si blocca finché non viene ricevuto un messaggio, o "evento", (con la funzione PeekMessage() come alternativa non bloccante). Dopo alcune elaborazioni facoltative, chiamerà DispatchMessage() , che invia il messaggio al relativo gestore, noto anche come WindowProc . Normalmente, i messaggi che non hanno WindowProc() speciale vengono inviati a DefWindowProc , quello predefinito. DispatchMessage () chiama il WindowProc del HWND manico del messaggio (registrato con il RegisterClass () la funzione).

Ordinazione dei messaggi

Versioni più recenti di Microsoft Windows garantiscono al programmatore che i messaggi verranno consegnati al ciclo di messaggi di un'applicazione nell'ordine in cui sono stati percepiti dal sistema e dalle sue periferiche. Questa garanzia è essenziale quando si considerano le conseguenze di progettazione delle applicazioni multithread .

Tuttavia, alcuni messaggi hanno regole diverse, ad esempio i messaggi che vengono sempre ricevuti per ultimi oi messaggi con una priorità documentata diversa.

Sistema X Window

Ciclo di eventi Xlib

Le applicazioni X che utilizzano direttamente Xlib sono costruite attorno alla XNextEventfamiglia di funzioni; XNextEventsi blocca finché un evento non viene visualizzato nella coda degli eventi, dopodiché l'applicazione lo elabora in modo appropriato. Il ciclo degli eventi di Xlib gestisce solo gli eventi del sistema a finestre; le applicazioni che devono essere in grado di attendere su altri file e dispositivi potrebbero costruire il proprio ciclo di eventi da primitive come ConnectionNumber, ma in pratica tendono a utilizzare il multithreading .

Pochissimi programmi usano Xlib direttamente. Nel caso più comune, i toolkit GUI basati su Xlib di solito supportano l'aggiunta di eventi. Ad esempio, i toolkit basati su Xt Intrinsics hanno XtAppAddInput()e XtAppAddTimeout().

Si noti che non è sicuro chiamare le funzioni Xlib da un gestore di segnale, perché l'applicazione X potrebbe essere stata interrotta in uno stato arbitrario, ad esempio all'interno di XNextEvent. Vedere [1] per una soluzione per X11R5, X11R6 e Xt.

Ciclo di eventi GLib

Il ciclo di eventi GLib è stato originariamente creato per l'uso in GTK, ma ora viene utilizzato anche in applicazioni non GUI, come D-Bus . La risorsa interrogata è la raccolta di descrittori di file a cui l'applicazione è interessata; il blocco del polling verrà interrotto se arriva un segnale o scade un timeout (ad es. se l'applicazione ha specificato un timeout o un task inattivo). Sebbene GLib disponga del supporto integrato per il descrittore di file e gli eventi di terminazione figlio, è possibile aggiungere un'origine evento per qualsiasi evento che può essere gestito in un modello di preparazione-controllo-invio. [2]

Le librerie applicative costruite sul ciclo di eventi GLib includono GStreamer e i metodi I/O asincroni di GnomeVFS , ma GTK rimane la libreria client più visibile. Gli eventi dal sistema di finestre (in X , letti dal socket X ) sono tradotti da GDK in eventi GTK ed emessi come segnali GLib sugli oggetti widget dell'applicazione.

Cicli di esecuzione di macOS Core Foundation

È consentito esattamente un CFrunLoop per thread e possono essere collegati arbitrariamente molte fonti e osservatori. Le fonti quindi comunicano con gli osservatori attraverso il ciclo di esecuzione, organizzando le code e l'invio dei messaggi.

Il CFRunLoop è astratto in Cocoa come NSRunLoop, che consente di accodare qualsiasi messaggio (equivalente a una chiamata di funzione in runtime non riflettenti ) per l'invio a qualsiasi oggetto.

Guarda anche

Riferimenti

link esterno