Coroutines · Quick recall Q&A
1 min readQuick recall Q&A
Q1: What is the difference between IAsyncEnumerable<T> and IObservable<T> (Rx)?
A: IAsyncEnumerable<T> is pull-based -- the consumer requests items via MoveNextAsync(). IObservable<T> is push-based -- the producer pushes items to subscribers. Use async enumerables when the consumer controls the pace; use Rx when the producer controls the pace and you need operators like Throttle, Buffer, or CombineLatest.
Q2: Can EF Core return IAsyncEnumerable<T> directly?
A: Yes. Call AsAsyncEnumerable() on any IQueryable<T> to get streaming results. This avoids ToListAsync() buffering. The database cursor remains open during iteration, so keep the DbContext alive for the duration.
Q3: How do you test async iterators?
A: Consume them with await foreach in a test and collect results into a list. Use System.Linq.Async's ToListAsync() for convenience. For cancellation tests, pass a pre-cancelled token and assert the sequence terminates.
[Fact]
public async Task StreamOrders_respects_cancellation()
{
using var cts = new CancellationTokenSource();
var items = new List<Order>();
await foreach (var order in _service.StreamOrdersAsync()
.WithCancellation(cts.Token))
{
items.Add(order);
if (items.Count == 3) cts.Cancel();
}
Assert.Equal(3, items.Count);
}
Q4: What is ChannelWriter.Complete() and why is it important?
A: Calling Complete() on the writer signals that no more items will be produced. The reader's ReadAllAsync() loop will finish gracefully once all buffered items are consumed. Forgetting to call Complete() causes the consumer to hang indefinitely.
Q5: How do you handle errors in an async stream?
A: Exceptions thrown inside an async iterator propagate to the consumer on the next MoveNextAsync() call. For Channel<T>, call Writer.Complete(exception) to signal an error. The consumer's ReadAllAsync() will throw that exception after draining buffered items.
Q6: What is the role of BoundedChannelFullMode?
A: It determines what happens when a bounded channel is full: Wait (default) blocks the producer, DropNewest discards the newest item, DropOldest discards the oldest, and DropWrite discards the item being written. Choose based on your domain -- Wait for lossless processing, DropOldest for live telemetry where stale data is irrelevant.