Novedades de C# 8: IAsyncEnumerable

      2 comentarios en Novedades de C# 8: IAsyncEnumerable
La imagen muestra el logotipo de c# 8.0 para la entrada sobre IAsyncEnumerable<T>

¿Y qué es eso de IAsyncEnumerable<T>? Hace algo más de un mes que se libreró .Net Core 3.0 y ya hemos hablado sobre algunas de las novedades que nos trae (que no son pocas…). Pero sin duda, una de las principales novedades que se liberaron junto a .Net Core 3, fue C# 8.0.

C# 8 nos ofrece una interesante cantidad de novedades de las que vamos a ir hablando durante las próximas semanas:

  • Tipos nullables
  • Rangos e índices
  • Declaraciones using
  • Interfaces con implementaciones por defecto
  • Reconocimiento de patrones
  • Asignaciones de uso combinado

Pero para mí el más importante en cuanto al impacto que puede tener en el rendimiento de nuestra aplicación, sin duda alguna, es «IAsyncEnumerable«.

Hay otros cambios que afecta mucho más al rendimiento, como Span<T> por ejemplo, pero es más difícil que lo usemos en nuestro día a día que IAsyncEnumerable

Hasta ahora, no teníamos manera de iterar un objeto de manera asíncrona medida que se va generando. ¿Qué quiere decir esto? ¡Pues fácil! Imagina que tenemos un método que nos devuelve una colección de 100 objetos y que, por ejemplo, tarda en generar cada objeto 1 segundo. Una vez que tenemos la colección lista, la iteramos con un foreach que tarda otro segundo en realizar un ciclo. Algo así:

async Task<IEnumerable<int>> GetCountIEnumerableAsync()
{
    var list = new List<int>();
    for (int i = 0; i < 100; i++)
    {
        await Task.Delay(1000); 
        list.Add(i);
    }

    return list;
}

//....

foreach (var valor in await GetCountIEnumerableAsync())
{
    //Acción que tarda un segundo en ejecutarse
}

Con el código anterior, asumiendo 1 segundo para generar cada objeto de la colección y un segundo para procesarlo, vamos a tardar 200 segundos en ejecutar el foreach completo. Hasta ahora, no nos quedaba otra que gestionar nosotros la situación para obtener los datos por un lado y procesarlos por otro para poder darle salida nada más obtenerlos, obligándonos a implementar una lógica extra que hay que mantener y sincronizar.

IAsyncEnumerable<T> al rescate

Como decíamos antes, con C# 8 se han introducido los streams asíncronos a través de la interfaz IAsyncEnumerable<T>. Esto lo que nos va a permitir es no tener que esperar a que la colección se obtenga completa antes de empezar a iterarla. Volviendo al ejemplo anterior, si en obtener cada dato tardamos un segundo, pero mientras obtenemos un nuevo dato procesamos el anterior, en vez de 200 segundos, solo vamos a tardar 101 (aproximadamente). Vamos a verlo con un poco de código:

async IAsyncEnumerable<int> GetCountIAsyncEnumerableAsync()
{
    for (int i = 0; i < 100; i++)
    {
        await Task.Delay(1000); 
        yield return i;
    }
}

//....

await foreach (var valor in GetCountIAsyncEnumerableAsync())
{
    //Acción que tarda un segundo en ejecutarse
}

Este código va a ir despachando cada uno de los objetos a medida que se van generando nuestro método «GetCountIAsyncEnumerableAsync». Como puedes comprobar esto es infinitamente más fácil y más legible. Imagina si tuviésemos que gestionar dos hilos independientes, uno para adquirir datos y otro para procesarlos…

Si los datos provienen de algún servicio externo como una base de datos o un API Rest (o cualquier otro origen en general). Como resultado, vamos a obtener una mejora bastante importante gracias a esto. Ya sea por aumentar el rendimiento, o por facilitarnos el código. ¿Aun no tienes claro cómo funciona?

Vamos a ver un ejemplo claro

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace IAsyncEnumerable
{
    internal static class Program
    {
        private static async Task Main(string[] args)
        {
            Console.WriteLine("Ejecutando foreach con Task<IEnumerable<int>>");
            foreach (var valor in await GetCountIEnumerableAsync())
            {
                Console.WriteLine($"Procesado el valor {valor}");
            }

            Console.WriteLine("Ejecutando foreach con IAsyncEnumerable<int>");
            await foreach (var valor in GetCountIAsyncEnumerableAsync())
            {
                Console.WriteLine($"Procesado el valor {valor}");
            }

            Console.Read();
        }

        private static async IAsyncEnumerable<int> GetCountIAsyncEnumerableAsync()
        {
            for (var i = 0; i < 3; i++)
            {
                await Task.Delay(1000);
                Console.WriteLine($"Añadido valor {i}");
                yield return i;
            }
        }

        private static async Task<IEnumerable<int>> GetCountIEnumerableAsync()
        {
            var list = new List<int>();
            for (var i = 0; i < 3; i++)
            {
                await Task.Delay(1000);
                Console.WriteLine($"Añadido valor {i}");
                list.Add(i);
            }
            return list;
        }
    }
}

En el código anterior, simplemente hemos implementado ambos casos, uno con IAsyncEnumerable<int> y otro con Task<IEnumerable<int>>. Si ejecutamos el código la salida que obtenemos es algo como esto:

La imagen muestra la salida por consola del ejemplo anterior, con y sin IAsyncEnumerable

Con la imagen se ve más claro cuál es el cambio de funcionamiento que nos porta esta nueva característica, pero… ¡Si tienes alguna duda, puedes dejar un comentario!

Durante las próximas semanas vamos a seguir hablando de las interesantes novedades que nos aporta C# 8. No te lo pierdas, realmente es muy interesante

2 pensamientos en “Novedades de C# 8: IAsyncEnumerable

Deja un comentario