Reputation: 7963
I'm trying to work out the advantage that IAsyncEnumerable<T>
brings over something like an IEnumerable<Task<T>>
.
I wrote the following class that allows me to wait for a sequence of numbers with a defined delay between each one:
class DelayedSequence : IAsyncEnumerable<int>, IEnumerable<Task<int>> {
readonly int _numDelays;
readonly TimeSpan _interDelayTime;
public DelayedSequence(int numDelays, TimeSpan interDelayTime) {
_numDelays = numDelays;
_interDelayTime = interDelayTime;
}
public IAsyncEnumerator<int> GetAsyncEnumerator(CancellationToken cancellationToken = default) {
async IAsyncEnumerable<int> ConstructEnumerable() {
for (var i = 0; i < _numDelays; ++i) {
await Task.Delay(_interDelayTime, cancellationToken);
yield return i;
}
}
return ConstructEnumerable().GetAsyncEnumerator();
}
public IEnumerator<Task<int>> GetEnumerator() {
IEnumerable<Task<int>> ConstructEnumerable() {
for (var i = 0; i < _numDelays; ++i) {
yield return Task.Delay(_interDelayTime).ContinueWith(_ => i);
}
}
return ConstructEnumerable().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
This class implements both IAsyncEnumerable<int>
and IEnumerable<Task<int>>
. I can iterate over it using both await foreach
and foreach
and get an identical result:
var delayedSequence = new DelayedSequence(5, TimeSpan.FromSeconds(1d));
await foreach (var i in delayedSequence) {
Console.WriteLine(i);
}
foreach (var t in delayedSequence) {
Console.WriteLine(await t);
}
Both iterations display the numbers 0 to 4 with a second's delay between each line.
Is the only advantage relating to the ability to cancel (i.e. the passed-in cancellationToken
)? Or is there some scenario I'm not seeing here?
Upvotes: 3
Views: 1510
Reputation: 456917
wait for a sequence of numbers with a defined delay between each one
The delay happens at different times. IEnumerable<Task<T>>
immediately returns the next element, which is then await
ed. IAsyncEnumerable<T>
await
s the next element.
IEnumerable<Task<T>>
is a (synchronous) enumeration where each element is asynchronous. This is the proper type to use when you have a known number of actions to perform and then each item asynchronously arrives independently.
For example, this type is commonly used when sending out multiple REST requests simultaneously. The number of REST requests to make is known at the start, and each request is Select
ed into an asynchronous REST call. The resulting enumerable (or collection) is then usually passed to await Task.WhenAll
to asynchronously wait for them all to complete.
IAsyncEnumerable<T>
is an asynchronous enumeration; i.e., its MoveNext
is actually an asynchronous MoveNextAsync
. This is the proper type to use when you have an unknown number of items to iterate, and getting the next (or next batch) is (potentially) asynchronous.
For example, this type is commonly used when paging results from an API. In this case, you have no idea how many elements will eventually be returned. In fact, you may not even know if you are at the end until after retrieving the next page of results. So even determining whether there is a next item is an asynchronous operation.
Upvotes: 9