Async streams with IAsyncEnumerable in C Sharp (C#) - Time & Space Complexity
When using async streams with IAsyncEnumerable, it's important to understand how the time to process items grows as the number of items increases.
We want to know how the total work changes as more data is streamed asynchronously.
Analyze the time complexity of the following code snippet.
public async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
for (int i = 0; i < count; i++)
{
await Task.Delay(10); // Simulate async work
yield return i;
}
}
public async Task ProcessNumbersAsync(int count)
{
await foreach (var number in GenerateNumbersAsync(count))
{
Console.WriteLine(number);
}
}
This code asynchronously generates numbers one by one, simulating a delay, and processes each number as it arrives.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The
forloop insideGenerateNumbersAsyncthat runscounttimes. - How many times: Exactly once per item, so
counttimes.
Each item causes one loop iteration with a small async delay and a yield. As the number of items grows, the total time grows proportionally.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | 10 async yields and delays |
| 100 | 100 async yields and delays |
| 1000 | 1000 async yields and delays |
Pattern observation: The total work grows linearly as the number of items increases.
Time Complexity: O(n)
This means the time to process all items grows directly in proportion to the number of items streamed.
[X] Wrong: "Because the method is async, it runs instantly regardless of input size."
[OK] Correct: Async means the method can pause and resume, but it still processes each item one by one, so total time depends on how many items there are.
Understanding async streams helps you explain how programs handle data that arrives over time, a useful skill when working with real-world data sources like files, networks, or user input.
"What if we removed the await Task.Delay(10) inside the loop? How would the time complexity change?"
Practice
IAsyncEnumerable<T> in C#?Solution
Step 1: Understand async streams concept
IAsyncEnumerable<T>allows data to be produced and consumed asynchronously, item by item.Step 2: Compare with other options
Options B, C, and D describe synchronous collections, exception handling, or threading, which are not the main purpose ofIAsyncEnumerable<T>.Final Answer:
To asynchronously stream data one item at a time without blocking. -> Option DQuick Check:
Async streams = async item-by-item data [OK]
- Confusing async streams with synchronous collections
- Thinking it automatically uses multiple threads
- Believing it handles exceptions directly
GetNumbersAsync()?Solution
Step 1: Recall async stream consumption syntax
To consumeIAsyncEnumerable<T>, useawait foreachto await each item asynchronously.Step 2: Check each option
foreach (var num in GetNumbersAsync()) { Console.WriteLine(num); } missesawait, so it won't compile. for await (var num in GetNumbersAsync()) { Console.WriteLine(num); } uses invalid syntaxfor await. async foreach (var num in GetNumbersAsync()) { Console.WriteLine(num); } uses invalid keywordasync foreach.Final Answer:
await foreach (var num in GetNumbersAsync()) { Console.WriteLine(num); } -> Option BQuick Check:
Use await foreach to read async streams [OK]
- Omitting 'await' before foreach
- Using 'for await' instead of 'await foreach'
- Writing 'async foreach' which is invalid
async IAsyncEnumerable<int> GetNumbers()
{
for (int i = 1; i <= 3; i++)
{
await Task.Delay(10);
yield return i;
}
}
async Task Run()
{
await foreach (var n in GetNumbers())
{
Console.Write(n);
}
}
Run().GetAwaiter().GetResult();Solution
Step 1: Understand the async stream generation
The method yields numbers 1, 2, 3 with a small delay between each.Step 2: Analyze the output in Run()
Theawait foreachprints each number without spaces or newlines, so output is "123".Final Answer:
123 -> Option CQuick Check:
Yielded numbers print consecutively as '123' [OK]
- Expecting spaces between numbers
- Thinking the delay prevents output
- Assuming an error due to async usage
async IAsyncEnumerable<int> CountToThree()
{
for (int i = 1; i <= 3; i++)
{
Task.Delay(10);
yield return i;
}
}Solution
Step 1: Check async delay usage
Task.Delay(10) returns a task but is not awaited, so delay does not pause execution.Step 2: Validate yield return in async streams
Usingyield returninasync IAsyncEnumerable<T>is valid, so Cannot use yield return in async methods is wrong.Final Answer:
Missing await before Task.Delay(10); -> Option AQuick Check:
Await async calls inside async streams [OK]
- Forgetting to await Task.Delay
- Thinking yield return is invalid in async streams
- Confusing return type with Task<IAsyncEnumerable>
Solution
Step 1: Check async stream signature and yield
Async streams must be declared asasync IAsyncEnumerable<T>and useyield returninside.Step 2: Validate each option
async IAsyncEnumerable<int> EvenNumbers() { for (int i = 1; i <= 10; i++) { if (i % 2 == 0) yield return i; } } lacks an async delay, so it is incomplete. async IAsyncEnumerable<int> EvenNumbers() { for (int i = 2; i <= 10; i += 2) { await Task.Delay(10); yield return i; } } correctly usesasync IAsyncEnumerable<int>, awaits delay, and yields even numbers. IAsyncEnumerable<int> EvenNumbers() { for (int i = 2; i <= 10; i += 2) yield return i; } is missing async and await, so not an async stream. async Task<IAsyncEnumerable<int>> EvenNumbers() { for (int i = 2; i <= 10; i += 2) yield return i; } returnsTask<IAsyncEnumerable>which is incorrect for async streams.Final Answer:
async IAsyncEnumerable<int> EvenNumbers() { for (int i = 2; i <= 10; i += 2) { await Task.Delay(10); yield return i; } } -> Option AQuick Check:
Async streams need async keyword and await inside [OK]
- Omitting await inside async streams
- Returning Task<IAsyncEnumerable> instead of IAsyncEnumerable
- Not marking method async when using yield return asynchronously
Ready to prove it?
Pass the test and this pattern moves unknown
