The problem

public async Task
Task<IEnumerable<User>>
Represents an asynchronous operation that can return a value.
<
Task<IEnumerable<User>>
Represents an asynchronous operation that can return a value.
IEnumerable
IEnumerable<User>
Exposes the enumerator, which supports a simple iteration over a collection of a specified type.
<
IEnumerable<User>
Exposes the enumerator, which supports a simple iteration over a collection of a specified type.
User
User
>
IEnumerable<User>
Exposes the enumerator, which supports a simple iteration over a collection of a specified type.
>
Task<IEnumerable<User>>
Represents an asynchronous operation that can return a value.
GetUsers
Task<IEnumerable<User>> Example.GetUsers()
()
{
var
List<User>
Represents a strongly typed list of objects that can be accessed by index. Provides methods to search, sort, and manipulate lists.
allResults
List<User>? allResults
= new List
List<User>
Represents a strongly typed list of objects that can be accessed by index. Provides methods to search, sort, and manipulate lists.
<
List<User>
Represents a strongly typed list of objects that can be accessed by index. Provides methods to search, sort, and manipulate lists.
User
User
>
List<User>
Represents a strongly typed list of objects that can be accessed by index. Provides methods to search, sort, and manipulate lists.
();
var
string
Represents text as a sequence of UTF-16 code units.
nextUrl
string? nextUrl
= "https://account.zendesk.com/api/v2/users.json";
while (nextUrl
string? nextUrl
!=
Task<IEnumerable<User>> Example.GetUsers()
null)
{
var
Task<IEnumerable<User>> Example.GetUsers()
page = await _client
HttpClient Example._client
.GetAsync
Task<HttpResponseMessage> HttpClient.GetAsync(string? requestUri)
Send a GET request to the specified Uri as an asynchronous operation.
requestUri — The Uri the request is sent to.
The task object representing the asynchronous operation.
InvalidOperationException — The requestUri must be an absolute URI or BaseAddress must be set.
HttpRequestException — The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
TaskCanceledException — .NET Core and .NET 5 and later only: The request failed due to timeout.
UriFormatException — The provided request URI is not valid relative or absolute URI.
(nextUrl
string? nextUrl
)
.Content.ReadAsAsync<UsersListResponse
UsersListResponse
>();
allResults
List<User>? allResults
.AddRange
void List<User>.AddRange(IEnumerable<User> collection)
Adds the elements of the specified collection to the end of the List`1.
collection — The collection whose elements should be added to the end of the List`1. The collection itself cannot be , but it can contain elements that are , if type T is a reference type.
ArgumentNullException — collection is .
(page.Users
Task<IEnumerable<User>> Example.GetUsers()
);
nextUrl
string? nextUrl
= page.NextPage
Task<IEnumerable<User>> Example.GetUsers()
;
// eg "https://account.zendesk.com/api/v2/users.json?page=2"
}
return allResults
List<User>? allResults
;
}

Take a look at the above code, you may have run into this familiar issue yourself. We’d like to represent the pageable results of this HTTP API in our types, while still be asynchronous.

Traditionally there would be 4 ways to approach this;

  1. Block on the async code with .Result/.GetAwaiter().GetResult() or .Wait(). This is not a good idea.
  2. The above approach of awaiting each asynchronous task, but doing it all eagerly and returning the complete results in a materialised collection. This defeats the purpose of this paging API, and more generally we lose the laziness of the problem we are trying to model.
  3. We flip the return type to IEnumerable<Task<User>>. This would require that we trust any consumers of this code to await the result of each task after every enumeration. There are ways to enforce this at runtime, and throw an exception if it’s not consumed correctly, however this ultimately is a misleading type, and the shape of the type doesn’t communicate it’s hidden constraints.
  4. We don’t try returning a single type such as Task<IEnumerable<T>> and we model it ourselves. This can be a good idea, but we lose the benefits of having a familiar type to work with.

Well it’s about time we adopted a new type and end this madness. That’s what IAsyncEnumerable<T> is for.

About Ix.Async

Currently IAsyncEnumerable<T> is a concept which exists in a few places, with no singular definition. The version I will be using today lives in the Reactive Extensions repo, in a fork that is based off of the latest C# 8.0 proposal.

Reactive Extensions (Rx) is the home of Observable implementation and extensions, it is also home to a sibling project named Interactive Extensions (Ix for short). Rx has lots of extensions and tools for composing pushed based sequences, and Ix is very similar but for pull based sequences (IEnumerable<T>). The part I am interested in for this post is the async part, which I’ll be referring to as Ix.Async, this is shipped in it’s own nuget package, and I will generally be referring to the IAsyncEnumerable<T> definition that lives here (although this will map trivially to other implementations).

In the near future, C# 8.0 will introduce Async Streams (I prefer the term Sequence, as Stream is already a different .NET concept) as a language feature, and there will be a new definition of IAsyncEnumerable<T> it will work with, but that doesn’t stop us using Ix.Async today, either using the current definition which slightly differs from the C# 8.0 proposal, or building the fork with the latest definition in.

Definition

public interface IAsyncEnumerable
IAsyncEnumerable<T>
<
IAsyncEnumerable<T>
out T
T
>
IAsyncEnumerable<T>
{
IAsyncEnumerator
IAsyncEnumerator<T>
<
IAsyncEnumerator<T>
T
T
>
IAsyncEnumerator<T>
GetAsyncEnumerator
IAsyncEnumerator<T> IAsyncEnumerable<T>.GetAsyncEnumerator()
();
}
public interface IAsyncEnumerator
IAsyncEnumerator<T>
<
IAsyncEnumerator<T>
out T
T
>
IAsyncEnumerator<T>
:
IAsyncEnumerator<T>
IAsyncDisposable
IAsyncDisposable
{
T
T
Current
T IAsyncEnumerator<T>.Current
{ get; }
ValueTask
ValueTask<bool>
Provides a value type that wraps a Task`1 and a TResult, only one of which is used.
<
ValueTask<bool>
Provides a value type that wraps a Task`1 and a TResult, only one of which is used.
bool
bool
Represents a Boolean ( or ) value.
>
ValueTask<bool>
Provides a value type that wraps a Task`1 and a TResult, only one of which is used.
MoveNextAsync
ValueTask<bool> IAsyncEnumerator<T>.MoveNextAsync()
();
}
public interface IAsyncDisposable
IAsyncDisposable
{
ValueTask
ValueTask
Provides an awaitable result of an asynchronous operation.
DisposeAsync
ValueTask IAsyncDisposable.DisposeAsync()
();
}

This is the definition of IAsyncEnumerable<T> from the C# 8.0 proposal, it should look very familiar, it is just IEnumerable<T> with an async MoveNext method, as you might expect.

async-enum12

We can now see the relationship with IObservable<T> and IEnumerable<T>.

Being in this familiar family means that we don’t have to learn new concepts to start consuming and composing operations over this type.

IAsyncEnumerable
IAsyncEnumerable<Bar>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
<
IAsyncEnumerable<Bar>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
Bar
Bar
>
IAsyncEnumerable<Bar>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
ConvertGoodFoosToBars
IAsyncEnumerable<Bar> Example.ConvertGoodFoosToBars(IAsyncEnumerable<Foo> items)
(IAsyncEnumerable
IAsyncEnumerable<Foo>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
<
IAsyncEnumerable<Foo>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
Foo
Foo
>
IAsyncEnumerable<Foo>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
items
IAsyncEnumerable<Foo> items
)
{
return items
IAsyncEnumerable<Foo> items
.Where
IAsyncEnumerable<Bar> Example.ConvertGoodFoosToBars(IAsyncEnumerable<Foo> items)
(foo => foo.IsGood
IAsyncEnumerable<Bar> Example.ConvertGoodFoosToBars(IAsyncEnumerable<Foo> items)
)
.Select
IAsyncEnumerable<Bar> Example.ConvertGoodFoosToBars(IAsyncEnumerable<Foo> items)
(foo => Bar
Bar
.FromFoo
Bar Bar.FromFoo(Foo f)
(foo));
}

These extension methods are immediately understandable to us and are ubiquitous in C# already.

Producing sequences

All of this would be pretty academic if we couldn’t generate sequences to be consumed. Today there are a few options.

1. Implement the IAsyncEnumerable<T> and IAsyncEnumerator<T> interfaces directly

You can do this, and for performance critical code, this might be the most suitable approach.

It does require a fair bit of boilerplate code however, so here is a starting point:

// A starting point for your own IAsyncEnumerable extensions
public static class AsyncEnumerableExtensions
AsyncEnumerableExtensions
{
public static IAsyncEnumerable
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
<
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
T
T
>
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
MyExtensionMethod
IAsyncEnumerable<T> AsyncEnumerableExtensions.MyExtensionMethod<T>(IAsyncEnumerable<T> source)
<
IAsyncEnumerable<T> AsyncEnumerableExtensions.MyExtensionMethod<T>(IAsyncEnumerable<T> source)
T
IAsyncEnumerable<T> AsyncEnumerableExtensions.MyExtensionMethod<T>(IAsyncEnumerable<T> source)
>
IAsyncEnumerable<T> AsyncEnumerableExtensions.MyExtensionMethod<T>(IAsyncEnumerable<T> source)
(this IAsyncEnumerable
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
<
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
T
T
>
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
source
IAsyncEnumerable<T> source
)
{
return new MyAsyncEnumerable
AsyncEnumerableExtensions.MyAsyncEnumerable<T>
<
AsyncEnumerableExtensions.MyAsyncEnumerable<T>
T
T
>
AsyncEnumerableExtensions.MyAsyncEnumerable<T>
(source
IAsyncEnumerable<T> source
);
}
public struct MyAsyncEnumerable
AsyncEnumerableExtensions.MyAsyncEnumerable<T>
<
AsyncEnumerableExtensions.MyAsyncEnumerable<T>
T
T
>
AsyncEnumerableExtensions.MyAsyncEnumerable<T>
:
AsyncEnumerableExtensions.MyAsyncEnumerable<T>
IAsyncEnumerable
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
<
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
T
T
>
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
{
readonly IAsyncEnumerable
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
<
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
T
T
>
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
enumerable
IAsyncEnumerable<T> AsyncEnumerableExtensions.MyAsyncEnumerable<T>.enumerable
;
internal MyAsyncEnumerable
AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerable(IAsyncEnumerable<T> enumerable)
(IAsyncEnumerable
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
<
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
T
T
>
IAsyncEnumerable<T>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
enumerable
IAsyncEnumerable<T> enumerable
)
{
this
IAsyncEnumerable<T> AsyncEnumerableExtensions.MyAsyncEnumerable<T>.enumerable
.enumerable
IAsyncEnumerable<T> AsyncEnumerableExtensions.MyAsyncEnumerable<T>.enumerable
= enumerable
IAsyncEnumerable<T> enumerable
;
}
public IAsyncEnumerator
IAsyncEnumerator<T>
Supports a simple asynchronous iteration over a generic collection.
<
IAsyncEnumerator<T>
Supports a simple asynchronous iteration over a generic collection.
T
T
>
IAsyncEnumerator<T>
Supports a simple asynchronous iteration over a generic collection.
GetAsyncEnumerator
IAsyncEnumerator<T> AsyncEnumerableExtensions.MyAsyncEnumerable<T>.GetAsyncEnumerator()
()
{
return new MyAsyncEnumerator
AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator
(enumerable
IAsyncEnumerable<T> AsyncEnumerableExtensions.MyAsyncEnumerable<T>.enumerable
.GetAsyncEnumerator
IAsyncEnumerator<T> IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken cancellationToken = default(CancellationToken))
Returns an enumerator that iterates asynchronously through the collection.
cancellationToken — A CancellationToken that may be used to cancel the asynchronous iteration.
An enumerator that can be used to iterate asynchronously through the collection.
());
}
public struct MyAsyncEnumerator
AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator
:
AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator
IAsyncEnumerator
IAsyncEnumerator<T>
Supports a simple asynchronous iteration over a generic collection.
<
IAsyncEnumerator<T>
Supports a simple asynchronous iteration over a generic collection.
T
T
>
IAsyncEnumerator<T>
Supports a simple asynchronous iteration over a generic collection.
{
readonly IAsyncEnumerator
IAsyncEnumerator<T>
Supports a simple asynchronous iteration over a generic collection.
<
IAsyncEnumerator<T>
Supports a simple asynchronous iteration over a generic collection.
T
T
>
IAsyncEnumerator<T>
Supports a simple asynchronous iteration over a generic collection.
enumerator
IAsyncEnumerator<T> AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator.enumerator
;
internal MyAsyncEnumerator
AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator.MyAsyncEnumerator(IAsyncEnumerator<T> enumerator)
(IAsyncEnumerator
IAsyncEnumerator<T>
Supports a simple asynchronous iteration over a generic collection.
<
IAsyncEnumerator<T>
Supports a simple asynchronous iteration over a generic collection.
T
T
>
IAsyncEnumerator<T>
Supports a simple asynchronous iteration over a generic collection.
enumerator
IAsyncEnumerator<T> enumerator
)
{
this
IAsyncEnumerator<T> AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator.enumerator
.enumerator
IAsyncEnumerator<T> AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator.enumerator
= enumerator
IAsyncEnumerator<T> enumerator
;
}
public ValueTask
ValueTask
Provides an awaitable result of an asynchronous operation.
DisposeAsync
ValueTask AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator.DisposeAsync()
()
{
return enumerator
IAsyncEnumerator<T> AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator.enumerator
.DisposeAsync
ValueTask IAsyncDisposable.DisposeAsync()
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously.
A task that represents the asynchronous dispose operation.
();
}
public T
T
Current
T AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator.Current
=> enumerator
IAsyncEnumerator<T> AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator.enumerator
.Current
T IAsyncEnumerator<T>.Current
Gets the element in the collection at the current position of the enumerator.
The element in the collection at the current position of the enumerator.
;
public ValueTask
ValueTask<bool>
Provides a value type that wraps a Task`1 and a TResult, only one of which is used.
<
ValueTask<bool>
Provides a value type that wraps a Task`1 and a TResult, only one of which is used.
bool
bool
Represents a Boolean ( or ) value.
>
ValueTask<bool>
Provides a value type that wraps a Task`1 and a TResult, only one of which is used.
MoveNextAsync
ValueTask<bool> AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator.MoveNextAsync()
()
{
return enumerator
IAsyncEnumerator<T> AsyncEnumerableExtensions.MyAsyncEnumerable<T>.MyAsyncEnumerator.enumerator
.MoveNextAsync
ValueTask<bool> IAsyncEnumerator<T>.MoveNextAsync()
Advances the enumerator asynchronously to the next element of the collection.
A ValueTask`1 that will complete with a result of if the enumerator was successfully advanced to the next element, or if the enumerator has passed the end of the collection.
();
}
}
}
}

2. Use the static helper methods in Ix.NET

IAsyncEnumerable
IAsyncEnumerable<int>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
<
IAsyncEnumerable<int>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
int
int
Represents a 32-bit signed integer.
>
IAsyncEnumerable<int>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
GenerateWithIx
IAsyncEnumerable<int> Example.GenerateWithIx()
()
{
return AsyncEnumerable
IAsyncEnumerable<int> Example.GenerateWithIx()
.CreateEnumerable
IAsyncEnumerable<int> Example.GenerateWithIx()
(
() =>
{
var
int
Represents a 32-bit signed integer.
current
int current
= 0;
async Task
Task<bool>
Represents an asynchronous operation that can return a value.
<
Task<bool>
Represents an asynchronous operation that can return a value.
bool
bool
Represents a Boolean ( or ) value.
>
Task<bool>
Represents an asynchronous operation that can return a value.
f
Task<bool> f(CancellationToken ct)
(CancellationToken
CancellationToken
Propagates notification that operations should be canceled.
ct
CancellationToken ct
)
{
await Task
Task
Represents an asynchronous operation.
. Delay
Task Task.Delay(TimeSpan delay)
Creates a task that completes after a specified time interval.
delay — The time span to wait before completing the returned task, or to wait indefinitely.
A task that represents the time delay.
ArgumentOutOfRangeException — delay represents a negative time interval other than . -or- The delay argument's TotalMilliseconds property is greater than 4294967294 on .NET 6 and later versions, or MaxValue on all previous versions.
(TimeSpan
TimeSpan
Represents a time interval.
.FromSeconds
TimeSpan TimeSpan.FromSeconds(double value)
Returns a TimeSpan that represents a specified number of seconds, where the specification is accurate to the nearest millisecond.
value — A number of seconds, accurate to the nearest millisecond.
An object that represents value.
OverflowException — value is less than MinValue or greater than MaxValue. -or- value is PositiveInfinity. -or- value is NegativeInfinity.
ArgumentException — value is equal to NaN.
(0.5));
current
int current
++
Task<bool> f(CancellationToken ct)
;
return true;
}
return AsyncEnumerable
IAsyncEnumerable<int> Example.GenerateWithIx()
.CreateEnumerator
IAsyncEnumerable<int> Example.GenerateWithIx()
(
moveNext
IAsyncEnumerable<int> Example.GenerateWithIx()
:
IAsyncEnumerable<int> Example.GenerateWithIx()
f
Task<bool> f(CancellationToken ct)
,
current
IAsyncEnumerable<int> Example.GenerateWithIx()
:
IAsyncEnumerable<int> Example.GenerateWithIx()
() => current
int current
,
dispose
IAsyncEnumerable<int> Example.GenerateWithIx()
:
IAsyncEnumerable<int> Example.GenerateWithIx()
() => { }
);
});
}

3. Use CXuesong.AsyncEnumerableExtensions

I wanted to build something like this myself, and then I found this library, so I don’t need to! Credit to Chen, this is a great library.

// using CXuesong.AsyncEnumerableExtensions
async Task
Task
Represents an asynchronous operation.
Generator
Task Generator(IAsyncEnumerableSink<int> sink)
(IAsyncEnumerableSink
IAsyncEnumerableSink<int>
<
IAsyncEnumerableSink<int>
int
int
Represents a 32-bit signed integer.
>
IAsyncEnumerableSink<int>
sink
IAsyncEnumerableSink<int> sink
)
{
var
int
Represents a 32-bit signed integer.
i
int i
= 1;
while (true)
{
await Task
Task
Represents an asynchronous operation.
.Delay
Task Task.Delay(TimeSpan delay)
Creates a task that completes after a specified time interval.
delay — The time span to wait before completing the returned task, or to wait indefinitely.
A task that represents the time delay.
ArgumentOutOfRangeException — delay represents a negative time interval other than . -or- The delay argument's TotalMilliseconds property is greater than 4294967294 on .NET 6 and later versions, or MaxValue on all previous versions.
(TimeSpan
TimeSpan
Represents a time interval.
.FromSeconds
TimeSpan TimeSpan.FromSeconds(double value)
Returns a TimeSpan that represents a specified number of seconds, where the specification is accurate to the nearest millisecond.
value — A number of seconds, accurate to the nearest millisecond.
An object that represents value.
OverflowException — value is less than MinValue or greater than MaxValue. -or- value is PositiveInfinity. -or- value is NegativeInfinity.
ArgumentException — value is equal to NaN.
(0.5));
await sink
IAsyncEnumerableSink<int> sink
.YieldAndWait
Task IAsyncEnumerableSink<int>.YieldAndWait(int item)
(i
int i
++
Task IAsyncEnumerableSink<int>.YieldAndWait(int item)
);
}
}
AsyncEnumerableFactory
AsyncEnumerableFactory
.FromAsyncGenerator
object AsyncEnumerableFactory.FromAsyncGenerator<int>(Func<IAsyncEnumerableSink<int>, Task> gen)
<
object AsyncEnumerableFactory.FromAsyncGenerator<int>(Func<IAsyncEnumerableSink<int>, Task> gen)
int
int
Represents a 32-bit signed integer.
>
object AsyncEnumerableFactory.FromAsyncGenerator<int>(Func<IAsyncEnumerableSink<int>, Task> gen)
(Generator
Task Generator(IAsyncEnumerableSink<int> sink)
);

This library offers a very nice and simple way to express sequences. You build an async function that takes a IAsyncEnumberableSink<T> (defined by the library), and returns a Task. Now you can do your awaits, but when you want to yield an item to the sequence, you call sink.YieldAndWait(value) where sink is that parameter.

4. Coming soon to a C# 8.0 near you

Today you cannot use the async keyword and iterator methods together, so having an async iterator method would require a new language feature. Well good news, it’s in the works, take a sneak peak here.

Here is a snippet showing what it could look like.

static async IAsyncEnumerable
IAsyncEnumerable<int>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
<
IAsyncEnumerable<int>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
int
int
Represents a 32-bit signed integer.
>
IAsyncEnumerable<int>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
Mylterator
IAsyncEnumerable<int> Example.Mylterator()
()
{
try
{
for (int
int
Represents a 32-bit signed integer.
i
int i
= 0; i
int i
<
IAsyncEnumerable<int> Example.Mylterator()
100; i
int i
++
IAsyncEnumerable<int> Example.Mylterator()
)
{
await Task
Task
Represents an asynchronous operation.
.Delay
Task Task.Delay(int millisecondsDelay)
Creates a task that completes after a specified number of milliseconds.
millisecondsDelay — The number of milliseconds to wait before completing the returned task, or -1 to wait indefinitely.
A task that represents the time delay.
ArgumentOutOfRangeException — The millisecondsDelay argument is less than -1.
(1000);
yield return i
int i
;
}
}
finally
{
await Task
Task
Represents an asynchronous operation.
.Delay
Task Task.Delay(int millisecondsDelay)
Creates a task that completes after a specified number of milliseconds.
millisecondsDelay — The number of milliseconds to wait before completing the returned task, or -1 to wait indefinitely.
A task that represents the time delay.
ArgumentOutOfRangeException — The millisecondsDelay argument is less than -1.
(200);
Console
Console
Represents the standard input, output, and error streams for console applications. This class cannot be inherited.
.WriteLine
void Console.WriteLine(string? value)
Writes the specified string value, followed by the current line terminator, to the standard output stream.
value — The value to write.
IOException — An I/O error occurred.
("finally");
}
}

Consuming sequencing

We can produce sequences, but that won’t be much use to us if we cannot consume them.

1. ForEachAsync

Just like the .ForEach(...) extension method on List<T>, we have .ForEachAsync(...) from Ix.Async, this lets us do work on each item, and gives us a Task to await to drive the whole chain of pull based work.

await seq.ForEachAsync(x => Console
Console
Represents the standard input, output, and error streams for console applications. This class cannot be inherited.
.WriteLine
void Console.WriteLine()
Writes the current line terminator to the standard output stream.
IOException — An I/O error occurred.
(x));

Unfortunately, dogmatism fails here, ForEachAsync is suffixed with Async because it returns a Task and operates asynchronously, however the delegate it takes is synchronous, this led me to build a method that can take an async delegate and name it ForEachAsyncButActuallyAsync. :facepalm:

await seq.ForEachAsyncButActuallyAsync(x => Console
Console
Represents the standard input, output, and error streams for console applications. This class cannot be inherited.
.WriteLine
void Console.WriteLine()
Writes the current line terminator to the standard output stream.
IOException — An I/O error occurred.
(x));

2. C# 8.0 foreach

Again, we have language support on the way. Here’s what it would look like:

var
IAsyncEnumerable<int>
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
asyncSequence
IAsyncEnumerable<int>? asyncSequence
= GetMyAsyncSequence
IAsyncEnumerable<int> GetMyAsyncSequence(CancellationToken cancellationToken)
(cancellationToken
CancellationToken cancellationToken
:
IAsyncEnumerable<int> GetMyAsyncSequence(CancellationToken cancellationToken)
ct
CancellationToken ct
);
await foreach (var
int
Represents a 32-bit signed integer.
item
int item
in asyncSequence
IAsyncEnumerable<int>? asyncSequence
)
{
..
Range Range.All.get
.
}

Design Decisions

One of the problems that has meant that we’ve had to wait so long a first class IAsyncEnumberable<T> and language features is because there are many design decisions that need answering, for example;

  • Does IAsyncEnumerator<T> implement IDisposable or a new async version (IAsyncDisposable)? Update IAsyncDisposable it is!
  • If there is going to be an IAsyncDisposable, should the language support the using syntax for it?
  • Does the CancellationToken get passed into MoveNext each move or GetEnumerator once? Update CancellationTokens are not going to be handled by syntax, so you should flow it into the IAsyncEnumerable<T> types yourself.
  • Should it be MoveNext, or MoveNextAsync? Update MoveNextAsync wins!
  • Should MoveNextAsync return a Task<bool> or a ValueTask<bool>? Update ValueTask<bool> has it!
  • In the foreach syntax, where does the await modifier go? Outside the brackets? (Yes, of course, what sort of monster do you take me for?)
  • In the foreach syntax, how do you do the equivalent of .ConfigureAwait(false)? Update like this.
  • Will the foreach syntax look for the type, or the pattern? await doesn’t just apply to Task for example.

and that’s just what comes immediately to mind, the more you think, the more you uncover.

Who is using it today?

There are a couple of large projects using this today:

  • Entity Framework Core - Currently using an internal definition, but there is talk of plans to use whatever comes in C# 8.
  • Google Cloud Platform Libraries - This one was a bit of a surprise to me. If you install any Google Cloud package, it will reference their Core package, which uses and references Ix.Async. One of the members of the team that builds this is (the) Jon Skeet, so that’s quite an endorsement!

Stay tuned, there is more to come on this topic.