¿Qué tiene de especial IAsyncEnumerable en .NET Core 3.0?

La traducción del artículo se preparó antes del inicio del curso "C # Developer" .










Una de las características más importantes de .NET Core 3.0 y C # 8.0 es la nueva característica IAsyncEnumerable<T>(también conocida como subproceso asincrónico). Pero, ¿qué tiene de especial él? ¿Qué podemos hacer ahora que antes era imposible?



En este artículo veremos qué tareas se IAsyncEnumerable<T>pretende resolver, cómo implementarlo en nuestras propias aplicaciones y por qué lo IAsyncEnumerable<T>reemplazará en muchas situaciones. Vea todas las características nuevas de .NET Core 3Task<IEnumerable<T>>







Vida antes IAsyncEnumerable<T>



Quizás la mejor manera de explicar por qué IAsyncEnumerable<T>es tan útil es mirar los problemas que enfrentamos antes.



Imagina que estamos creando una biblioteca para interactuar con datos y necesitamos un método que solicite algunos datos de una tienda o API. Por lo general, este método devuelve así:Task<IEnumerable<T>>



public async Task<IEnumerable<Product>> GetAllProducts()


Para implementar este método, generalmente solicitamos datos de forma asincrónica y los devolvemos cuando se completa. El problema con esto se vuelve más obvio cuando necesitamos realizar múltiples llamadas asincrónicas para obtener datos. Por ejemplo, nuestra base de datos o API puede devolver datos en páginas completas, como esta implementación usando Azure Cosmos DB:



public async Task<IEnumerable<Product>> GetAllProducts()
{
    Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
    var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
    var products = new List<Product>();
    while (iterator.HasMoreResults)
    {
        foreach (var product in await iterator.ReadNextAsync())
        {
            products.Add(product);
        }
    }
    return products;
}


Observe que recorremos todos los resultados en un ciclo while, instanciamos objetos de producto, los colocamos en una lista y, finalmente, devolvemos todo. Esto es bastante ineficiente, especialmente en grandes conjuntos de datos.



Quizás podamos crear una implementación más eficiente modificando nuestro método para que devuelva resultados en una página completa a la vez:



public IEnumerable<Task<IEnumerable<Product>>> GetAllProducts()
{
    Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
    var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
    while (iterator.HasMoreResults)
    {
        yield return iterator.ReadNextAsync().ContinueWith(t => 
        {
            return (IEnumerable<Product>)t.Result;
        });
    }
}


La persona que llama usará el método como este:



foreach (var productsTask in productsRepository.GetAllProducts())
{
    foreach (var product in await productsTask)
    {
        Console.WriteLine(product.Name);
    }
}


Esta implementación es más eficiente, pero el método ahora regresa . Como podemos ver en el código de llamada, llamar al método y procesar los datos no es intuitivo. Más importante aún, la paginación es un detalle de implementación de un método de acceso a datos que la persona que llama no necesita conocer.IEnumerable<Task<IEnumerable<Product>>>



IAsyncEnumerable<T> apresurándose a ayudar



Lo que realmente queremos hacer es obtener datos de nuestra base de datos de forma asincrónica y devolver los resultados a la persona que llama a medida que los recibe.



En el código síncrono, un método que devuelve IEnumerable puede usar una declaración de retorno de rendimiento para devolver cada pieza de datos a la persona que llama, ya que proviene de la base de datos.



public IEnumerable<Product> GetAllProducts()
{
    Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
    var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
    while (iterator.HasMoreResults)
    {
        foreach (var product in iterator.ReadNextAsync().Result)
        {
            yield return product;
        }
    }
}


Sin embargo, ¡ NUNCA HAGA ESTO ! El código anterior convierte una llamada de base de datos asincrónica en una llamada de bloqueo y no escala.



¡Si tan solo pudiéramos usar yield returncon métodos asincrónicos! Era imposible ... hasta ahora.



IAsyncEnumerable<T>se introdujo en .NET Core 3 (.NET Standard 2.1). Proporciona un enumerador que tiene un método MoveNextAsync()que podría esperarse. Esto significa que el iniciador puede realizar llamadas asincrónicas mientras (en el medio) recibe los resultados.



En lugar de devolver Task <IEnumerable <T >>, nuestro método ahora puede devolver IAsyncEnumerable<T>y usar yield return para pasar datos.



public async IAsyncEnumerable<Product> GetAllProducts()
{
    Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
    var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
    while (iterator.HasMoreResults)
    {
        foreach (var product in await iterator.ReadNextAsync())
        {
            yield return product;
        }
    }
}


Para usar los resultados, necesitamos usar la nueva sintaxis await foreach()disponible en C # 8:



await foreach (var product in productsRepository.GetAllProducts())
{
    Console.WriteLine(product);
}


Esto es mucho mejor. El método produce datos a medida que ingresan. El código de llamada usa los datos a su propio ritmo.



IAsyncEnumerable<T> y ASP.NET Core



A partir de .NET Core 3 Preview 7 , ASP.NET puede devolver un IAsyncEnumerable desde una acción de controlador de API. Esto significa que podemos devolver los resultados de nuestro método directamente, pasando datos de la base de datos a la respuesta HTTP.



[HttpGet]
public IAsyncEnumerable<Product> Get()
    => productsRepository.GetAllProducts();


Reemplazo paraTask<IEnumerable<T>>IAsyncEnumerable<T>



A medida que pasa el tiempo a medida que nos familiarizamos con .NET Core 3 y .NET Standard 2.1, se espera que IAsyncEnumerable<T>se use en lugares donde normalmente <usaríamos Task IEnumerable>.



Espero ver apoyo IAsyncEnumerable<T>en las bibliotecas. En este artículo, vimos un código similar para consultar datos con el SDK de Azure Cosmos DB 3.0:



var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
while (iterator.HasMoreResults)
{
    foreach (var product in await iterator.ReadNextAsync())
    {
        Console.WriteLine(product.Name);
    }
}


Como en nuestros ejemplos anteriores, el SDK de Cosmos DB nativo también nos carga con los detalles de la implementación de la paginación, lo que dificulta el procesamiento de los resultados de las consultas.



Para ver cómo se vería si GetItemQueryIterator<Product>()regresara IAsyncEnumerable<T>, podemos crear un método de extensión en FeedIterator:



public static class FeedIteratorExtensions
{
    public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(this FeedIterator<T> iterator)
    {
        while (iterator.HasMoreResults)
        {
            foreach(var item in await iterator.ReadNextAsync())
            {
                yield return item;
            }
        }
    }
}


Ahora podemos manejar los resultados de nuestras consultas de una manera mucho mejor:



var products = container
    .GetItemQueryIterator<Product>("SELECT * FROM c")
    .ToAsyncEnumerable();
await foreach (var product in products)
{
    Console.WriteLine(product.Name);
}


Resumen



IAsyncEnumerable<T>- es una adición bienvenida a .NET y en muchos casos hará que su código sea más agradable y eficiente. Puede obtener más información sobre esto en estos recursos:








Patrón de diseño estatal






Lee mas:






All Articles