C# - Apresentando streams assíncronos

 Hoje vou apresentar o recurso streams assíncronos incluído na versão 8.0 da linguagem C#.

A linguagem C# apresenta suporte a métodos assíncronos e a métodos iteradores mas não tinha suporte para métodos iteradores assíncronos.

Isso mudou a partir da versão 8.0 quando foi incluído o recurso streams assíncronos.

A Interface IEnumerable permite realizar iterações sobre um tipo e se podemos iterar sobre uma lista é porque ela implementa IEnumerable.

Dessa forma o código a seguir :

var meses = new List<string>() { "janeiro", "fevereiro", "março", "abril"};

foreach (var mes in meses)
{
    Console.WriteLine(mes);
}

É possível pois a classe List<T> implementa a interface IEnumerable que implementa a interface IEnumerator que retornar um enumerador que pode ser usado para iterar através da coleção.

Ocorre que em um cenário real geralmente não temos a lista pronto para ser consumida mas obtemos a lista ou parte dela através da chamada de um método.

Quando obtemos um elemento por vez da lista podemos usar a instrução yield que implementa um iterador de forma direta de forma a gerar os valores um a um da lista.

Desta forma estamos criando um tipo iterável que retorna um IEnumerable como mostrar o trecho de código a seguir:

static private IEnumerable<string> GeraMeses()

{

   yield return "janeiro";

   yield return "fevereiro";

   yield return "março";

   yield return "abril";

}

foreach (var mes in GeraMeses())
{
    Console.WriteLine(mes);
}

Note que o que temos aqui na verdade é uma sequência de dados ou um stream e que estamos iterando sobre esse stream de dados de forma síncrona.

Mas e se desejarmos gerar os nomes dos meses de forma assíncrona ?

Usando streams assíncronos

Para poder tentar gerar os nomes dos meses de forma assíncrona devemos tornar o método assíncrono usando o modificador async e retornando um Task de IEnumerable, e, usar um await para simular uma tarefa :

static private async Task<IEnumerable<string>> GeraMeses()

{

   yield return "janeiro";

   yield return "fevereiro";
   await  Task.Delay(2000);

   yield return "março";

   yield return "abril";

}

foreach (var mes in GeraMeses())
{
    Console.WriteLine(mes);
}

Ao tentar fazer isso teremos um erro que indica  que não podemos iterar o tipo Task porque o iterável é a interface IEnumerable e
 task não contem uma definição para GetEnumerator.

É aqui que entram os streams assíncronos introduzidos no C# 8.0.

Podemos usar streams assíncronos para criar tipos enumeráveis que geram dados de forma assíncrona.

Para isso temos que cumprir 3 exigências :

1- O método deve ser assíncrono (async/await);
2- O tipo de retorno do método deve ser IAsyncEnumerable<T>;
3- O corpo do método deve conter pelo menos um yield return;


Aqui a interface IAsyncEnumerable expõe um enumerador que oferece iteração assíncrona sobre valores de um tipo especificado, e, a instrução return yield retornam elementos sucessivos no stream assíncrono.

Este recurso esta disponível no .NET 5.0,  .NET Core 3.0 e  no .NET Standard 2.1.

Vejamos então como aplicar isso ao nosso método GeraMeses para realizar o acesso assíncrono ao stream de meses.

static private async IAsyncEnumerable<IEnumerable<string> GeraMeses()

{

   yield return "janeiro";

   yield return "fevereiro";
   await  Task.Delay(2000);

   yield return "março";

   yield return "abril";

}

await foreach (var mes in GeraMeses())
{
    Console.WriteLine(mes);
}

Aqui usamos a interface IAsyncEnumerable no lugar de Task<IEnumerable> e invocamos o método usando um await na frente de foreach.

Dessa forma agora temos um Enumerable assíncrono que gera valores de forma assíncrona.

Os exemplos que eu usei foram bem simples de forma a facilitar o entendimento. No entanto este recurso geralmente é usado em cenário com acesso remoto para obter valores de um serviço web  onde temos uma grande quantidade de dados que serão processados de forma gradual.

E estamos conversados...

"E, ao pôr do sol, todos os que tinham enfermos de várias doenças lhos traziam; e, pondo as mãos sobre cada um deles, 'Jesus' os curava."
Lucas 4:40

Referências:


José Carlos Macoratti