Le coroutine si basano sul concetto di generatori in cui una funzione può produrre valori e successivamente essere ripresa per continuare l'esecuzione. Le coroutine forniscono un potente strumento per gestire le operazioni asincrone e possono migliorare notevolmente la qualità complessiva del codice.
Usi delle coroutine
Le coroutine sono necessarie per diversi motivi nella programmazione moderna, in particolare in linguaggi come C++. Ecco alcuni motivi chiave per cui le coroutine sono utili:
Le coroutine forniscono una soluzione elegante alla programmazione asincrona. Permettono di creare un codice che appare sequenziale e bloccante su cui è più semplice ragionare e comprendere. Le coroutine possono sospendere la propria esecuzione in punti specifici senza bloccare i thread, consentendo un'operazione parallela di altre attività. Per questo motivo, le risorse del sistema possono essere utilizzate in modo più efficace e la reattività aumenta nelle applicazioni che coinvolgono operazioni di I/O o in attesa di eventi esterni.
Potrebbero rendere il codice più facile da comprendere e mantenere. Eliminando le complesse catene di callback o macchine a stati, le coroutine consentono di scrivere il codice in uno stile più lineare e sequenziale. Ciò migliora l'organizzazione del codice, riduce la nidificazione e rende la logica facile da comprendere.
Le coroutine forniscono un modo strutturato per gestire la concorrenza e il parallelismo. Consentono di esprimere modelli di coordinamento complessi e flussi di lavoro asincroni utilizzando una sintassi più intuitiva. A differenza dei modelli di threading tradizionali in cui i thread potrebbero essere bloccati, le coroutine possono liberare le risorse del sistema e consentire un multitasking efficiente.
Creiamo alcuni esempi per dimostrare l'implementazione delle coroutine in C++.
Esempio 1: Coroutine di base
L'esempio di coroutine di base è fornito di seguito:
#include
#include
struttura Questo Corout {
struttura tipo_promessa {
ThisCorout get_return_object ( ) { ritorno { } ; }
standard :: sospendere_mai sospensione_iniziale ( ) { ritorno { } ; }
standard :: sospendere_mai final_sospendere ( ) noeccetto { ritorno { } ; }
vuoto eccezione non gestita ( ) { }
vuoto return_void ( ) { }
} ;
bool attendono_pronto ( ) { ritorno falso ; }
vuoto attendono_sospensione ( standard :: coroutine_handle <> H ) { }
vuoto wait_resume ( ) { standard :: cout << 'La Coroutine è ripresa.' << standard :: fine ; }
} ;
Questo Corout foo ( ) {
standard :: cout << 'La Coroutine è iniziata.' << standard :: fine ;
co_aspetta std :: sospendere_sempre { } ;
co_ritorno ;
}
int principale ( ) {
auto cr = pippo ( ) ;
standard :: cout << 'La Coroutine viene creata.' << standard :: fine ;
cr. wait_resume ( ) ;
standard :: cout << 'Coroutine finita.' << standard :: fine ;
ritorno 0 ;
}
Esaminiamo il codice fornito in precedenza e lo spieghiamo in dettaglio:
Dopo aver incluso i file header richiesti, definiamo la struttura “ThisCorout” che rappresenta una coroutine. All'interno di 'ThisCorout', viene definita un'altra struttura che è 'promise_type' che gestisce la promessa coroutine. Questa struttura fornisce varie funzioni richieste dal macchinario coroutine.
All'interno delle parentesi utilizziamo la funzione get_return_object(). Restituisce l'oggetto coroutine stesso. In questo caso, restituisce un oggetto “ThisCorout” vuoto. Quindi, viene richiamata la funzione partial_suspend() che determina il comportamento al primo avvio della coroutine. std::suspend_never significa che la coroutine non deve essere sospesa inizialmente.
Successivamente, abbiamo la funzione final_suspend() che determina il comportamento quando la coroutine sta per terminare. std::suspend_never significa che la coroutine non deve essere sospesa prima della sua finalizzazione.
Se una coroutine lancia un'eccezione, viene richiamato il metodo unhandled_exception(). In questo esempio, è una funzione vuota, ma puoi gestire le eccezioni secondo necessità. Quando la coroutine termina senza restituire un valore, viene richiamato il metodo return_void(). In questo caso, è anche una funzione vuota.
Definiamo anche tre funzioni membro all'interno di 'ThisCorout'. La funzione wait_ready() viene chiamata per verificare se la coroutine è pronta per riprendere l'esecuzione. In questo esempio restituisce sempre false, il che indica che la coroutine non è pronta per riprendere immediatamente. Quando la coroutine sta per essere sospesa, viene chiamato il metodo wait_suspend(). In questo caso si tratta di una funzione vuota, il che significa che non è necessaria alcuna sospensione. Il programma chiama wait_resume() quando la coroutine viene ripresa dopo la sospensione. Genera semplicemente un messaggio che informa che la coroutine è stata ripresa.
Le righe successive del codice definiscono la funzione coroutine foo(). All'interno di foo(), iniziamo stampando un messaggio che informa che la coroutine è iniziata. Quindi, co_await std::suspend_always{} viene utilizzato per sospendere la coroutine e indica che può essere ripresa in un secondo momento. L'istruzione co_return viene utilizzata per terminare la coroutine senza restituire alcun valore.
Nella funzione main(), costruiamo un oggetto “cr” di tipo “ThisCorout” chiamando foo(). Questo crea e avvia la coroutine. Quindi viene stampato un messaggio che informa che la coroutine è stata creata. Successivamente, chiamiamo wait_resume() sull'oggetto coroutine “cr” per riprenderne l'esecuzione. All'interno di wait_resume() viene stampato il messaggio “The Coroutine is Resume”. Infine, visualizziamo un messaggio che informa che la coroutine è completa prima che il programma termini.
Quando esegui questo programma, l'output è il seguente:
Esempio 2: Coroutine con parametri e snervamento
Ora, per questa illustrazione, forniamo un codice che dimostra l'uso delle coroutine con parametri e la resa in C++ per creare un comportamento simile a un generatore per produrre una sequenza di numeri.
#include#include
#include
struttura NOVITÀCoroutine {
struttura p_tipo {
standard :: vettore < int > valori ;
NOVITÀCoroutine get_return_object ( ) { ritorno { } ; }
standard :: sospendere_sempre sospensione_iniziale ( ) { ritorno { } ; }
standard :: sospendere_sempre final_sospendere ( ) noeccetto { ritorno { } ; }
vuoto eccezione non gestita ( ) { }
vuoto return_void ( ) { }
standard :: sospendere_sempre valore_rendimento ( int valore ) {
valori. respingere ( valore ) ;
ritorno { } ;
}
} ;
standard :: vettore < int > valori ;
struttura iteratore {
standard :: coroutine_handle <> chorus_handle ;
operatore bool != ( cost iteratore & altro ) cost { ritorno chorus_handle != altro. chorus_handle ; }
iteratore & operatore ++ ( ) { chorus_handle. riprendere ( ) ; ritorno * Questo ; }
int operatore * ( ) cost { ritorno chorus_handle. promettere ( ) . valori [ 0 ] ; }
} ;
inizio dell'iteratore ( ) { ritorno iteratore { standard :: coroutine_handle < p_tipo >:: da_promessa ( promettere ( ) ) } ; }
fine dell'iteratore ( ) { ritorno iteratore { nullptr } ; }
standard :: coroutine_handle < p_tipo > promettere ( ) { ritorno
standard :: coroutine_handle < p_tipo >:: da_promessa ( * Questo ) ; }
} ;
NOVITÀLa coroutine genera numeri ( ) {
co_rendimento 5 ;
co_rendimento 6 ;
co_rendimento 7 ;
}
int principale ( ) {
NOVITÀCoroutine nc = generareNumeri ( ) ;
per ( int valore : nc ) {
standard :: cout << valore << ' ' ;
}
standard :: cout << standard :: fine ;
ritorno 0 ;
}
Nel codice precedente, la struttura NEWCoroutine rappresenta un generatore basato su coroutine. Contiene una struttura annidata “p_type” che funge da tipo di promessa per la coroutine. La struttura p_type definisce le funzioni richieste dal meccanismo della coroutine come get_return_object(), partial_suspend(), final_suspend(), unhandled_exception() e return_void(). La struttura p_type include anche la funzione yield_value(int value) che viene utilizzata per produrre i valori dalla coroutine. Aggiunge il valore fornito al vettore dei valori.
La struttura NEWCoroutine include la variabile membro std::vettore
Usiamo la funzione Begin() per creare un iteratore all'inizio della coroutine ottenendo coro_handle dalla promessa p_type. Mentre la funzione end() crea un iteratore che rappresenta la fine della coroutine ed è costruito con un nullptr coro_handle. Successivamente, la funzione promise() viene utilizzata per restituire il tipo di promessa creando un coroutine_handle dalla promessa p_type. La funzione generateNumbers() è una coroutine che produce tre valori – 5, 6 e 7 – utilizzando la parola chiave co_yield.
Nella funzione main(), viene creata un'istanza di NEWCoroutine denominata 'nc' richiamando la coroutine generateNumbers(). Questo inizializza la coroutine e ne cattura lo stato. Un ciclo 'for' basato su intervallo viene utilizzato per scorrere i valori di 'nc' e viene stampato ciascun valore separato da uno spazio utilizzando std::cout.
L'output generato è il seguente:
Conclusione
Questo articolo illustra l'utilizzo delle coroutine in C++. Abbiamo discusso due esempi. Per la prima illustrazione, la coroutine di base viene creata in un programma C++ utilizzando le funzioni della coroutine. Mentre la seconda dimostrazione è stata effettuata utilizzando le coroutine parametrizzate e riuscendo a generare un comportamento simile a un generatore per creare una sequenza di numeri.