Flux asynchrones en C# (IAsyncEnumerable)
Apprenez les flux asynchrones en C# avec IAsyncEnumerable pour traiter les données progressivement avec des exemples.
Dans les applications modernes .NET, les données arrivent souvent progressivement et au fil du temps : flux réseau, lignes de journal, données de capteurs…
IAsyncEnumerable<T> permet de consommer ces flux de manière asynchrone et paresseuse (lazy).
Du côté producteur, on utilise async + yield return ; du côté consommateur, on utilise await foreach.
Qu’est-ce que IAsyncEnumerable ?
IAsyncEnumerable<T> permet de produire et de consommer une séquence de données asynchronement.
Contrairement à IEnumerable<T>, chaque étape peut impliquer une attente (par exemple à cause d’une opération I/O).
Pour consommer, on écrit await foreach ; à chaque itération, la boucle attend que l’élément suivant soit prêt.
// Consommation simple
await foreach (var item in GetNumbersAsync())
{
Console.WriteLine(item);
}
Itérateur asynchrone : async + yield return
Lorsqu’on écrit une méthode productrice asynchrone, le type de retour est IAsyncEnumerable<T>.
À l’intérieur du corps, on peut utiliser await pour attendre et yield return pour produire un élément.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
static async IAsyncEnumerable<int> GetNumbersAsync()
{
for (int i = 1; i <= 5; i++)
{
await Task.Delay(500); // Simule une opération I/O ou longue
yield return i; // un nombre toutes les 500 ms
}
}
// Consommation
await foreach (var n in GetNumbersAsync())
{
Console.WriteLine($"Reçu : {n}");
}
Consommation avec await foreach
await foreach lit les éléments d’un flux asynchrone sans bloquer l’interface utilisateur ni le thread.
À chaque étape, il attend que les données soient disponibles, puis poursuit la boucle.
await foreach (var log in LireLogsAsync())
{
// Traiter l’entrée dès qu’elle arrive
Traiter(log);
}
Gestion des erreurs et using
Lors de la consommation de flux asynchrones, on peut utiliser un bloc try/catch classique.
Si une erreur se produit dans le flux ou pendant la consommation, elle est levée pendant l’exécution du await foreach.
try
{
await foreach (var item in GetNumbersAsync())
Console.WriteLine(item);
}
catch (Exception ex)
{
Console.WriteLine("Erreur : " + ex.Message);
}
Pour les ressources asynchrones, utilisez IAsyncDisposable et await using.
await using var ressource = await OuvrirRessourceAsync();
await foreach (var x in ressource.StreamAsync())
{
// ...
}
Prise en charge de l’annulation (CancellationToken)
Pour arrêter les flux longs, on peut annuler la consommation.
Côté producteur, capturez le jeton avec [EnumeratorCancellation] et vérifiez-le périodiquement.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
static async IAsyncEnumerable<int> CompterAsync(
int debut, int nombre, [EnumeratorCancellation] CancellationToken ct = default)
{
for (int i = 0; i < nombre; i++)
{
ct.ThrowIfCancellationRequested();
await Task.Delay(300, ct);
yield return debut + i;
}
}
// Consommation
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)); // annuler après 1 s
await foreach (var n in CompterAsync(10, 100, cts.Token))
{
Console.WriteLine(n);
}
Comparaison : IEnumerable<Task<T>> vs IAsyncEnumerable<T>
- IEnumerable<Task<T>> : toutes les tâches sont produites d’un coup, puis consommées ; le contrôle de flux est faible.
- IAsyncEnumerable<T> : les éléments sont produits à la demande ; l’utilisation mémoire et le timing sont plus efficaces.
Lecture asynchrone ligne par ligne (exemple de wrapper)
L’exemple suivant crée un flux qui lit un fichier asynchronement, ligne par ligne.
Chaque ligne est envoyée au consommateur avec yield return dès qu’elle est disponible.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
static async IAsyncEnumerable<string> LireLignesAsync(string chemin)
{
using var fs = new FileStream(chemin, FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 4096, useAsync: true);
using var sr = new StreamReader(fs);
while (!sr.EndOfStream)
{
var ligne = await sr.ReadLineAsync(); // à chaque ligne lue
if (ligne is not null)
yield return ligne;
}
}
// Consommation
await foreach (var ligne in LireLignesAsync("log.txt"))
{
Console.WriteLine(ligne);
}
Filtrage simple sur un flux (Pipeline)
Les flux asynchrones peuvent être chaînés pour créer un pipeline : produire → filtrer → transformer → consommer.
static async IAsyncEnumerable<int> FiltrerAsync(IAsyncEnumerable<int> source)
{
await foreach (var n in source)
{
if (n % 2 == 0) // garder les nombres pairs
yield return n * 10; // transformation
}
}
// Utilisation
await foreach (var x in FiltrerAsync(GetNumbersAsync()))
{
Console.WriteLine(x); // 20, 40, ...
}
Scénario UI : await foreach dans WPF
Dans WPF, les flux longs ne figent pas l’interface utilisateur ;
celle-ci peut être mise à jour à chaque nouvel élément.
Si une mise à jour de l’interface est nécessaire, revenez au thread UI avec Dispatcher.
// Exemple : lire les valeurs d’un capteur et les afficher dans l’interface
await foreach (var v in LireCapteurAsync())
{
Dispatcher.Invoke(() => txtStatut.Text = $"Dernière valeur : {v}");
}
Conseils et points d’attention
- Produisez les données progressivement avec
yield return; évitez de charger de grandes listes en mémoire d’un coup. - Prévoyez la prise en charge de l’annulation (
CancellationToken) ; les flux longs doivent pouvoir être arrêtés par l’utilisateur. - Gérez les erreurs au bon niveau avec
try/catch; capturez-les et journalisez-les côté consommateur. - Utilisez
Dispatcherpour les mises à jour UI ; n’accédez pas aux contrôles depuis un thread non-UI.
TL;DR
- IAsyncEnumerable<T> sert à gérer des flux de données asynchrones et paresseux.
- Producteur :
async+yield return; Consommateur :await foreach. - Utilisez
[EnumeratorCancellation]etCancellationTokenpour la gestion d’annulation. - IAsyncEnumerable<T> peut être plus efficace qu’un IEnumerable<Task<T>> en termes de mémoire et de performance.
- Si des mises à jour UI sont nécessaires, utilisez
Dispatcherpour revenir sur le thread principal.
Articles connexes
Bases de la programmation asynchrone en C# (async/await)
Apprenez async et await en C# pour créer des applications réactives avec des tâches asynchrones et des exemples pratiques.
Bibliothèque parallèle TPL et programmation parallèle en C#
Apprenez la Task Parallel Library et la programmation parallèle en C# avec Task, Parallel et des exemples pratiques.
Gestion des processus et des threads en C#
Apprenez la gestion des processus et des threads en C# pour contrôler l’exécution et les ressources système.