Um dos recursos mais importantes do .NET Core 3.0 e C # 8.0 é o novo
IAsyncEnumerable<T>(também conhecido como thread assíncrono). Mas o que há de tão especial nele? O que podemos fazer agora que antes era impossível?
Neste artigo, veremos quais tarefas ele
IAsyncEnumerable<T>pretende resolver, como implementá-lo em nossos próprios aplicativos e por que ele IAsyncEnumerable<T>o substituirá em muitas situações.
Confira todos os novos recursos do .NET Core 3Task<IEnumerable<T>>
Vida antes IAsyncEnumerable<T>
IAsyncEnumerable<T>Talvez a melhor maneira de explicar por que
IAsyncEnumerable<T>ele é tão útil seja examinar os problemas que enfrentamos antes.
Imagine que estamos criando uma biblioteca para interagir com os dados e precisamos de um método que solicita alguns dados de uma loja ou API. Normalmente, esse método retorna assim:
Task<IEnumerable<T>>
public async Task<IEnumerable<Product>> GetAllProducts()
Para implementar esse método, geralmente solicitamos dados de forma assíncrona e os retornamos quando concluído. O problema com isso se torna mais óbvio quando precisamos fazer várias chamadas assíncronas para obter dados. Por exemplo, nosso banco de dados ou API pode retornar dados em páginas inteiras, como esta implementação usando o 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 percorremos todos os resultados em um loop while, instanciamos objetos de produto, os colocamos em uma lista e, finalmente, retornamos tudo. Isso é bastante ineficiente, especialmente em grandes conjuntos de dados.
Talvez possamos criar uma implementação mais eficiente, modificando nosso método para que ele retorne os resultados de uma página inteira de cada 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;
});
}
}
O chamador usará o método como este:
foreach (var productsTask in productsRepository.GetAllProducts())
{
foreach (var product in await productsTask)
{
Console.WriteLine(product.Name);
}
}
Essa implementação é mais eficiente, mas o método agora retorna . Como podemos ver no código de chamada, chamar o método e processar os dados não é intuitivo. Mais importante, a paginação é um detalhe de implementação de um método de acesso a dados que o chamador não precisa saber.
IEnumerable<Task<IEnumerable<Product>>>
IAsyncEnumerable<T> correndo para ajudar
IAsyncEnumerable<T>O que realmente queremos fazer é buscar dados de nosso banco de dados de forma assíncrona e passar os resultados de volta ao chamador à medida que são recebidos.
No código síncrono, um método que retorna IEnumerable pode usar uma instrução de retorno de rendimento para retornar cada parte dos dados para o chamador conforme eles vêm do banco de dados.
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;
}
}
}
No entanto, NUNCA FAÇA ISSO ! O código acima transforma uma chamada de banco de dados assíncrona em uma chamada de bloqueio e não aumenta.
Se pudéssemos usar
yield returncom métodos assíncronos! Era impossível ... até agora.
IAsyncEnumerable<T>foi introduzido no .NET Core 3 (.NET Standard 2.1). Ele fornece um enumerador que possui um método MoveNextAsync()que pode ser esperado. Isso significa que o iniciador pode fazer chamadas assíncronas enquanto (no meio) recebe os resultados.
Em vez de retornar Task
<IEnumerable <T >>, nosso método agora pode retornar IAsyncEnumerable<T>e usar yield return para passar dados.
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 os resultados, precisamos usar a nova sintaxe
await foreach()disponível em C # 8:
await foreach (var product in productsRepository.GetAllProducts())
{
Console.WriteLine(product);
}
Isso é muito melhor. O método produz dados conforme eles chegam. O código de chamada usa os dados em seu próprio ritmo.
IAsyncEnumerable<T> e ASP.NET Core
IAsyncEnumerable<T>A partir do .NET Core 3 Preview 7 , o ASP.NET pode retornar um IAsyncEnumerable de uma ação do controlador de API. Isso significa que podemos retornar os resultados do nosso método diretamente - passando dados do banco de dados de maneira eficiente para uma resposta HTTP.
[HttpGet]
public IAsyncEnumerable<Product> Get()
=> productsRepository.GetAllProducts();
Substituição paraTask<IEnumerable<T>>IAsyncEnumerable<T>
Task<IEnumerable<T>>IAsyncEnumerable<T>Conforme o tempo passa, conforme nos familiarizamos com o .NET Core 3 e .NET Standard 2.1, espera-
IAsyncEnumerable<T>se que ele seja usado em lugares onde normalmente <usaríamos a Tarefa IEnumerable>.
Estou ansioso para ver o suporte
IAsyncEnumerable<T>nas bibliotecas. Neste artigo, vimos um código semelhante para consultar dados usando o SDK do 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 em nossos exemplos anteriores, o Cosmos DB SDK nativo também nos carrega com os detalhes da implementação da paginação, tornando difícil processar os resultados da consulta.
Para ver como ficaria se
GetItemQueryIterator<Product>()retornasse IAsyncEnumerable<T>, podemos criar um método de extensão em 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;
}
}
}
}
Agora podemos lidar com os resultados de nossas consultas de uma maneira muito mais agradável:
var products = container
.GetItemQueryIterator<Product>("SELECT * FROM c")
.ToAsyncEnumerable();
await foreach (var product in products)
{
Console.WriteLine(product.Name);
}
Resumo
IAsyncEnumerable<T>- é uma adição bem-vinda ao .NET e, em muitos casos, tornará seu código mais agradável e eficiente. Você pode aprender mais sobre isso nestes recursos:
- Tutorial: gerar e consumir streams assíncronos usando C # 8.0 e .NET Core 3.0
- Propostas de linguagem C # - Streams Async
Padrão de design de estado
Consulte Mais informação:
- Práticas recomendadas para melhorar o desempenho em C #
- Entity Framework Core