As corrotinas são baseadas no conceito de geradores onde uma função pode produzir valores e posteriormente ser retomada para continuar a execução. As corrotinas fornecem uma ferramenta poderosa para gerenciar operações assíncronas e podem melhorar muito a qualidade geral do seu código.
Usos de corrotinas
As corrotinas são necessárias por vários motivos na programação moderna, especialmente em linguagens como C++. Aqui estão alguns motivos principais pelos quais as corrotinas são benéficas:
As corrotinas fornecem uma solução elegante para programação assíncrona. Eles permitem criar um código que parece sequencial e bloqueador, mais simples de raciocinar e compreender. As corrotinas podem suspender sua execução em pontos específicos sem bloquear as threads, possibilitando o funcionamento paralelo de outras tarefas. Por causa disso, os recursos do sistema podem ser utilizados de forma mais eficaz e a capacidade de resposta é aumentada em aplicações que envolvem operações de E/S ou espera por eventos externos.
Eles podem tornar o código mais fácil de entender e manter. Ao eliminar as complexas cadeias de retorno de chamada ou máquinas de estado, as corrotinas permitem que o código seja escrito em um estilo mais linear e sequencial. Isso melhora a organização do código, reduz o aninhamento e facilita a compreensão da lógica.
As corrotinas fornecem uma maneira estruturada de lidar com a simultaneidade e o paralelismo. Eles permitem expressar padrões de coordenação complexos e fluxos de trabalho assíncronos usando uma sintaxe mais intuitiva. Ao contrário dos modelos tradicionais de threading, onde os threads podem ser bloqueados, as corrotinas podem liberar recursos do sistema e permitir uma multitarefa eficiente.
Vamos criar alguns exemplos para demonstrar a implementação de corrotinas em C++.
Exemplo 1: Corrotinas Básicas
O exemplo básico de corrotinas é fornecido a seguir:
#include
#include
estrutura Este Corout {
estrutura tipo_promessa {
ThisCorout get_return_object ( ) { retornar { } ; }
padrão :: suspender_nunca suspensão_inicial ( ) { retornar { } ; }
padrão :: suspender_nunca suspensão_final ( ) não, exceto { retornar { } ; }
vazio exceção_não tratada ( ) { }
vazio retorno_void ( ) { }
} ;
bool aguarda_ready ( ) { retornar falso ; }
vazio aguardar_suspender ( padrão :: coroutine_handle <> h ) { }
vazio aguardar_resume ( ) { padrão :: corte << 'A Corrotina é retomada.' << padrão :: fim ; }
} ;
Este Corout foo ( ) {
padrão :: corte << 'A corrotina começou.' << padrão :: fim ;
co_await std :: suspender_sempre { } ;
co_retorno ;
}
interno principal ( ) {
auto cr = foo ( ) ;
padrão :: corte << 'A corrotina foi criada.' << padrão :: fim ;
cr. aguardar_resume ( ) ;
padrão :: corte << 'Corotina terminada.' << padrão :: fim ;
retornar 0 ;
}
Vamos examinar o código fornecido anteriormente e explicá-lo em detalhes:
Depois de incluir os arquivos de cabeçalho necessários, definimos a estrutura “ThisCorout” que representa uma corrotina. Dentro de “ThisCorout”, é definida outra estrutura que é “promise_type” que trata da promessa da corrotina. Essa estrutura fornece várias funções exigidas pelo maquinário de co-rotina.
Dentro dos colchetes, utilizamos a função get_return_object(). Ele retorna o próprio objeto da co-rotina. Neste caso, ele retorna um objeto “ThisCorout” vazio. Em seguida, a função inicial_suspend() é invocada, determinando o comportamento quando a corrotina é iniciada pela primeira vez. O std::suspend_never significa que a corrotina não deve ser suspensa inicialmente.
Depois disso, temos a função final_suspend() que determina o comportamento quando a corrotina está prestes a terminar. O std::suspend_never significa que a corrotina não deve ser suspensa antes de sua finalização.
Se uma corrotina lançar uma exceção, o método unhandled_exception() será invocado. Neste exemplo, é uma função vazia, mas você pode tratar as exceções conforme necessário. Quando a corrotina termina sem produzir um valor, o método return_void() é invocado. Neste caso, também é uma função vazia.
Também definimos três funções-membro em “ThisCorout”. A função wait_ready() é chamada para verificar se a corrotina está pronta para retomar a execução. Neste exemplo, sempre retorna falso, o que indica que a corrotina não está pronta para ser retomada imediatamente. Quando a corrotina for suspensa, o método wait_suspend() é chamado. Aqui, é uma função vazia, o que significa que nenhuma suspensão é necessária. O programa chama await_resume() quando a corrotina é retomada após a suspensão. Ele apenas gera uma mensagem informando que a corrotina foi retomada.
As próximas linhas do código definem a função de co-rotina foo(). Dentro de foo(), começamos imprimindo uma mensagem informando que a corrotina foi iniciada. Em seguida, co_await std::suspend_always{} é usado para suspender a corrotina e indica que ela pode ser retomada posteriormente. A instrução co_return é usada para finalizar a corrotina sem retornar nenhum valor.
Na função main(), construímos um objeto “cr” do tipo “ThisCorout” chamando foo(). Isso cria e inicia a corrotina. Em seguida, é impressa uma mensagem informando que a corrotina foi criada. A seguir, chamamos await_resume() no objeto de co-rotina “cr” para retomar sua execução. Dentro de wait_resume(), a mensagem “A corrotina foi retomada” é impressa. Por fim, exibimos uma mensagem informando que a corrotina foi concluída antes do programa terminar.
Ao executar este programa, a saída é a seguinte:
Exemplo 2: Corrotina com Parâmetros e Rendimento
Agora, para esta ilustração, fornecemos um código que demonstra o uso de corrotinas com parâmetros e rendimento em C++ para criar um comportamento semelhante a um gerador para produzir uma sequência de números.
#include#include
#incluir
estrutura NEWCorotina {
estrutura tipo_p {
padrão :: vetor < interno > valores ;
NEWCoroutine get_return_object ( ) { retornar { } ; }
padrão :: suspender_sempre suspensão_inicial ( ) { retornar { } ; }
padrão :: suspender_sempre suspensão_final ( ) não, exceto { retornar { } ; }
vazio exceção_não tratada ( ) { }
vazio retorno_void ( ) { }
padrão :: suspender_sempre valor_rendimento ( interno valor ) {
valores. retrocesso ( valor ) ;
retornar { } ;
}
} ;
padrão :: vetor < interno > valores ;
estrutura iterador {
padrão :: coroutine_handle <> chorus_handle ;
operador booleano != ( const iterador & outro ) const { retornar chorus_handle != outro. chorus_handle ; }
iterador & operador ++ ( ) { chorus_handle. retomar ( ) ; retornar * esse ; }
interno operador * ( ) const { retornar chorus_handle. promessa ( ) . valores [ 0 ] ; }
} ;
início do iterador ( ) { retornar iterador { padrão :: coroutine_handle < tipo_p >:: de_promessa ( promessa ( ) ) } ; }
fim do iterador ( ) { retornar iterador { nullptr } ; }
padrão :: coroutine_handle < tipo_p > promessa ( ) { retornar
padrão :: coroutine_handle < tipo_p >:: de_promessa ( * esse ) ; }
} ;
NEWCoroutine generateNumbers ( ) {
co_rendimento 5 ;
co_rendimento 6 ;
co_rendimento 7 ;
}
interno principal ( ) {
NEWCoroutine nc = gerar números ( ) ;
para ( interno valor : não ) {
padrão :: corte << valor << ' ' ;
}
padrão :: corte << padrão :: fim ;
retornar 0 ;
}
No código anterior, a estrutura NEWCoroutine representa um gerador baseado em corrotina. Ele contém uma estrutura “p_type” aninhada que serve como tipo de promessa para a corrotina. A estrutura p_type define as funções exigidas pelo mecanismo de corrotina, como get_return_object(), inicial_suspend(), final_suspend(), unhandled_exception() e return_void(). A estrutura p_type também inclui a função yield_value(int value) que é usada para gerar os valores da corrotina. Adiciona o valor fornecido ao vetor de valores.
A estrutura NEWCoroutine inclui a variável de membro std::vector
Usamos a função Begin() para criar um iterador no início da corrotina, obtendo o coro_handle da promessa p_type. Enquanto a função end() cria um iterador que representa o fim da corrotina e é construído com um nullptr coro_handle. Depois disso, a função promessa() é utilizada para retornar o tipo de promessa criando um coroutine_handle a partir da promessa p_type. A função generateNumbers() é uma corrotina que produz três valores – 5, 6 e 7 – usando a palavra-chave co_yield.
Na função main(), uma instância de NEWCoroutine chamada “nc” é criada invocando a corrotina generateNumbers(). Isso inicializa a corrotina e captura seu estado. Um loop “for” baseado em intervalo é usado para iterar sobre os valores de “nc”, e cada valor é impresso, separado por um espaço usando std::cout.
A saída gerada é a seguinte:
Conclusão
Este artigo demonstra a utilização de corrotinas em C++. Discutimos dois exemplos. Para a primeira ilustração, a corrotina básica é criada em um programa C++ usando as funções de corrotina. Enquanto a segunda demonstração foi realizada utilizando as corrotinas com parâmetros e produzindo um comportamento semelhante ao de um gerador para criar uma sequência de números.