L'une des fonctionnalités les plus importantes de .NET Core 3.0 et C # 8.0 est la nouvelle fonctionnalité
IAsyncEnumerable<T>
(aka thread asynchrone). Mais qu'y a-t-il de si spécial chez lui? Que pouvons-nous faire maintenant qui était impossible auparavant?
Dans cet article, nous verrons quelles tâches il est
IAsyncEnumerable<
T>
prévu de résoudre, comment l'implémenter dans nos propres applications et pourquoi il IAsyncEnumerable<
T>
le remplacera dans de nombreuses situations.
Découvrez toutes les nouvelles fonctionnalités de .NET Core 3Task<IEnumerable<
T>>
La vie avant IAsyncEnumerable<
T>
IAsyncEnumerable<
T>La meilleure façon d'expliquer pourquoi
IAsyncEnumerable<
T>
il est si utile est peut-être d'examiner les problèmes que nous avons rencontrés auparavant.
Imaginez que nous créons une bibliothèque pour interagir avec les données et que nous avons besoin d'une méthode qui demande des données à un magasin ou à une API. Habituellement, cette méthode retourne comme ceci:
Task<
IEnumerable<
T>>
public async Task<IEnumerable<Product>> GetAllProducts()
Pour implémenter cette méthode, nous demandons généralement des données de manière asynchrone et les retournons une fois qu'elles sont terminées. Le problème avec cela devient plus évident lorsque nous devons effectuer plusieurs appels asynchrones pour obtenir des données. Par exemple, notre base de données ou notre API peut renvoyer des données dans des pages entières, comme cette implémentation à l'aide d'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;
}
Notez que nous parcourons tous les résultats dans une boucle while, instancions les objets produit, les plaçons dans une liste et finalement retournons le tout. Ceci est assez inefficace, en particulier sur les grands ensembles de données.
Peut-être pouvons-nous créer une implémentation plus efficace en modifiant notre méthode afin qu'elle renvoie les résultats une page entière à la fois:
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;
});
}
}
L'appelant utilisera la méthode comme ceci:
foreach (var productsTask in productsRepository.GetAllProducts())
{
foreach (var product in await productsTask)
{
Console.WriteLine(product.Name);
}
}
Cette implémentation est plus efficace, mais la méthode retourne maintenant . Comme nous pouvons le voir à partir du code d'appel, l'appel de la méthode et le traitement des données ne sont pas intuitifs. Plus important encore, la pagination est un détail d'implémentation d'une méthode d'accès aux données que l'appelant n'a pas besoin de connaître.
IEnumerable<
Task<
IEnumerable<
Product>>>
IAsyncEnumerable<
T>
se dépêcher d'aider
IAsyncEnumerable<
T>Ce que nous voulons vraiment faire, c'est récupérer les données de notre base de données de manière asynchrone et transmettre les résultats à l'appelant au fur et à mesure qu'ils sont reçus.
Dans le code synchrone, la méthode qui retourne IEnumerable peut utiliser l'instruction yield return pour renvoyer chaque élément de données à l'appelant tel qu'il provient de la base de données.
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;
}
}
}
Cependant, NE FAITES JAMAIS CELA ! Le code ci-dessus transforme un appel de base de données asynchrone en un appel de blocage et ne se met pas à l'échelle.
Si seulement nous pouvions utiliser des
yield return
méthodes asynchrones! C'était impossible ... jusqu'à maintenant.
IAsyncEnumerable<
T>
a été introduit dans .NET Core 3 (.NET Standard 2.1). Il fournit un énumérateur qui a une méthode MoveNextAsync()
attendue. Cela signifie que l'initiateur peut effectuer des appels asynchrones pendant (au milieu de) la réception des résultats.
Au lieu de renvoyer Task
<
IEnumerable <
T >>, notre méthode peut maintenant retourner IAsyncEnumerable<
T>
et utiliser yield return pour transmettre des données.
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;
}
}
}
Pour utiliser les résultats, nous devons utiliser la nouvelle syntaxe
await foreach()
disponible en C # 8:
await foreach (var product in productsRepository.GetAllProducts())
{
Console.WriteLine(product);
}
C'est beaucoup mieux. La méthode produit des données au fur et à mesure qu'elles arrivent. Le code appelant utilise les données à son propre rythme.
IAsyncEnumerable<
T>
et ASP.NET Core
IAsyncEnumerable<
T>À partir de .NET Core 3 Preview 7 , ASP.NET peut renvoyer un IAsyncEnumerable à partir d'une action de contrôleur d'API. Cela signifie que nous pouvons renvoyer les résultats de notre méthode directement - en passant efficacement les données de la base de données dans la réponse HTTP.
[HttpGet]
public IAsyncEnumerable<Product> Get()
=> productsRepository.GetAllProducts();
Remplacement pourTask<
IEnumerable<
T>>
IAsyncEnumerable<
T>
Task<
IEnumerable<
T>>IAsyncEnumerable<
T>Au fur et à mesure que nous nous familiarisons avec .NET Core 3 et .NET Standard 2.1, il devrait
IAsyncEnumerable<
T>
être utilisé dans des endroits où nous utiliserions normalement Task <
IEnumerable>.
J'ai hâte de voir du soutien
IAsyncEnumerable<
T>
dans les bibliothèques. Dans cet article, nous avons vu un code similaire pour interroger des données à l'aide du SDK 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);
}
}
Comme dans nos exemples précédents, le SDK Cosmos DB natif nous charge également des détails de l'implémentation de la pagination, ce qui rend difficile le traitement des résultats de la requête.
Pour voir à quoi cela pourrait ressembler s'il
GetItemQueryIterator<
Product>()
retournait à la place IAsyncEnumerable<
T>
, nous pouvons créer une méthode d'extension dans 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;
}
}
}
}
Nous pouvons désormais gérer les résultats de nos requêtes d'une manière beaucoup plus agréable:
var products = container
.GetItemQueryIterator<Product>("SELECT * FROM c")
.ToAsyncEnumerable();
await foreach (var product in products)
{
Console.WriteLine(product.Name);
}
Résumé
IAsyncEnumerable<
T>
- est un ajout bienvenu à .NET et, dans de nombreux cas, rendra votre code plus agréable et plus efficace. Vous pouvez en savoir plus à ce sujet sur ces ressources:
- Tutoriel: générer et consommer des flux asynchrones à l'aide de C # 8.0 et .NET Core 3.0
- Propositions de langage C # - Async Streams
Modèle de conception d'état