IAsyncEnumerable i await foreach
Strumieniuj dane asynchronicznie za pomocą IAsyncEnumerable, używaj yield w iteratorach asynchronicznych i odbieraj dane przez await foreach.
IAsyncEnumerable i await foreach to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.
Dlaczego IAsyncEnumerable?
IAsyncEnumerable<T> (wprowadzone w C# 8 i .NET Core 3) umożliwia asynchroniczne strumieniowanie — elementy są udostępniane pojedynczo, gdy stają się dostępne, bez wcześniejszego ładowania wszystkiego do pamięci. Można na przykład odczytywać wiersze kursora bazy danych jeden po drugim albo strumieniować odpowiedzi API.
Pisanie iteratora asynchronicznego
Należy zadeklarować metodę zwracającą IAsyncEnumerable<T> i użyć w niej yield return w metodzie asynchronicznej. Słowa kluczowe await i yield return naturalnie współdziałają.
public async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
for (int i = 0; i < count; i++)
{
await Task.Delay(100); // simulate async work per item
yield return i;
}
}Korzystanie z await foreach
Z elementów IAsyncEnumerable<T> należy korzystać za pomocą await foreach. Każda iteracja asynchronicznie oczekuje na następny element — konsument i producent są naturalnie synchronizowani.
await foreach (var number in GenerateNumbersAsync(10))
{
Console.WriteLine(number);
}
// With cancellation:
var cts = new CancellationTokenSource();
await foreach (var n in GenerateNumbersAsync(100)
.WithCancellation(cts.Token))
{
if (n > 50) { cts.Cancel(); break; }
Console.WriteLine(n);
}Strumieniowanie z bazy danych
EF Core obsługuje strumieniowanie wyników zapytań za pomocą AsAsyncEnumerable(). Wiersze są odczytywane pojedynczo, bez ładowania całego zestawu wyników do pamięci — ma to kluczowe znaczenie w przypadku dużych tabel.
public async IAsyncEnumerable<Product> StreamProductsAsync(
[EnumeratorCancellation] CancellationToken ct = default)
{
await foreach (var product in _db.Products
.Where(p => p.IsActive)
.AsAsyncEnumerable()
.WithCancellation(ct))
{
yield return product;
}
}Strumieniowanie odpowiedzi HTTP
Duże odpowiedzi można strumieniować do klientów bez buforowania całego ładunku. Klient otrzymuje dane stopniowo, podczas gdy serwer nadal je przetwarza.
app.MapGet("/stream/products", (ProductRepository repo) =>
{
// Returns an IAsyncEnumerable — ASP.NET Core streams it as JSON array
return repo.StreamAllAsync();
});
// Or with custom serialization:
app.MapGet("/stream/events", async (HttpResponse response, EventStore store) =>
{
response.ContentType = "application/json";
await foreach (var e in store.StreamAsync())
{
await response.WriteAsJsonAsync(e);
await response.Body.FlushAsync();
}
});Atrybut EnumeratorCancellation
Atrybut [EnumeratorCancellation] należy zastosować do parametru CancellationToken iteratora asynchronicznego, aby wywołanie WithCancellation() po stronie konsumenta przekazywało token do producenta.
public async IAsyncEnumerable<LogEntry> TailLogAsync(
string logFile,
[EnumeratorCancellation] CancellationToken ct = default)
{
while (!ct.IsCancellationRequested)
{
var newLines = await ReadNewLinesAsync(logFile, ct);
foreach (var line in newLines)
yield return new LogEntry(line);
await Task.Delay(500, ct);
}
}
// Consumer:
await foreach (var entry in TailLogAsync("/var/log/app.log")
.WithCancellation(cts.Token))
{
Console.WriteLine(entry.Message);
}LINQ dla IAsyncEnumerable
Pakiet NuGet System.Linq.Async udostępnia asynchroniczne operatory LINQ (WhereAwait, SelectAwait, ToListAsync) dla IAsyncEnumerable.
// dotnet add package System.Linq.Async
var expensiveProducts = await repo.StreamAllAsync()
.WhereAwait(async p => await pricing.IsExpensiveAsync(p.Id))
.SelectAwait(async p => await enricher.EnrichAsync(p))
.Take(10)
.ToListAsync();
// Or use built-in Where/Select that don't need async:
var names = repo.StreamAllAsync()
.Where(p => p.IsActive)
.Select(p => p.Name);Scalanie wielu strumieni asynchronicznych
Aby równocześnie przetwarzać wiele strumieni asynchronicznych, należy użyć Channel jako punktu agregacji — wielu producentów zapisuje dane, a jeden konsument je odczytuje.
async IAsyncEnumerable<T> Merge<T>(
params IAsyncEnumerable<T>[] streams)
{
var channel = Channel.CreateUnbounded<T>();
var producers = streams.Select(async stream =>
{
await foreach (var item in stream)
await channel.Writer.WriteAsync(item);
});
_ = Task.WhenAll(producers)
.ContinueWith(_ => channel.Writer.Complete());
await foreach (var item in channel.Reader.ReadAllAsync())
yield return item;
}Wydajność: buforowanie a strumieniowanie
Podczas przetwarzania dużych zbiorów danych należy zawsze używać strumieniowania. Wcześniejsze załadowanie wszystkiego do listy (ToListAsync()) przechowuje wszystkie dane jednocześnie w pamięci — strumieniowanie przetwarza jeden element naraz.
// BAD: loads all 1M records into memory
var allOrders = await _db.Orders.ToListAsync();
foreach (var order in allOrders)
await ProcessAsync(order);
// GOOD: processes one record at a time
await foreach (var order in _db.Orders.AsAsyncEnumerable())
await ProcessAsync(order);
// Memory usage: O(1) vs O(n)Obsługa błędów w iteratorach asynchronicznych
Całą konstrukcję await foreach należy umieścić w bloku try/catch, aby obsługiwać błędy pochodzące od producenta. Błędy iteratora są przekazywane konsumentowi w miejscu bieżącej iteracji.
try
{
await foreach (var item in RiskyStreamAsync())
{
await ProcessAsync(item);
}
}
catch (HttpRequestException ex)
{
Console.Error.WriteLine($"Stream error: {ex.Message}");
}
catch (OperationCanceledException)
{
Console.WriteLine("Stream cancelled.");
}Szybkie sprawdzenie
Jaka jest najważniejsza korzyść pamięciowa IAsyncEnumerable w porównaniu ze zwracaniem List<T>?
Podsumowanie: IAsyncEnumerable i await foreach
Najważniejsze wnioski:
IAsyncEnumerable<T>: asynchroniczne strumieniowanie elementów, po jednym narazyield returnw metodach asynchronicznych tworzy iteratory asynchroniczneawait foreach: konsumpcja strumienia z anulowaniem za pomocą.WithCancellation()[EnumeratorCancellation]: przekazuje token konsumenta do iteratora- EF Core: należy używać
AsAsyncEnumerable()w przypadku dużych zapytań, aby efektywnie korzystać z pamięci - Podczas przetwarzania dużych zbiorów danych należy używać strumieniowania zamiast
ToListAsync()
Często zadawane pytania
Czy lekcja „IAsyncEnumerable i await foreach” jest bezpłatna?
Tak — pełny tekst „IAsyncEnumerable i await foreach” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „IAsyncEnumerable i await foreach”?
Strumieniuj dane asynchronicznie za pomocą IAsyncEnumerable, używaj yield w iteratorach asynchronicznych i odbieraj dane przez await foreach. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć C# Academy?
Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „IAsyncEnumerable i await foreach”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?
Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- IAsyncEnumerable i await foreach
- System.Threading.Channels
- ValueTask i unikanie alokacji
- ConfigureAwait i kontekst synchronizacji