Esempi di coroutine C++

Esempi Di Coroutine C



Le coroutine forniscono una funzionalità del linguaggio che consente di scrivere il codice asincrono in modo più organizzato e lineare, promuovendo un approccio strutturato e sequenziale. Forniscono un meccanismo per mettere in pausa e riavviare l'esecuzione di una funzione in particolari istanze senza interrompere l'intero thread. Le coroutine sono utili quando si gestiscono attività che richiedono l'attesa di operazioni di I/O come la lettura da un file o l'invio di una chiamata di rete.

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 denominata 'values' che rappresenta i valori generati. All'interno di NEWCoroutine è presente un iteratore di strutture nidificate che consente di scorrere i valori generati. Contiene un coro_handle che è un handle per la coroutine e definisce gli operatori come !=, ++ e * per l'iterazione.

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.