In the previous installment, I discussed how to use iterators (
yield return) to create async methods. This time, we’re about to do almost the opposite – use async methods to implement async iterators.
Here’s what it looks like:
What are async iterators?
In .NET we use the
IEnumerator<T> interfaces to create forward-only iterators. The enumerator interface contains a
bool MoveNext() method, that when called, advances the iterator to the next item.
Async iterators replace this method with
Task<bool> MoveNext(), so that each step can be performed asynchronously. This is useful when the next item should be retrieved asynchronously – mainly because it incurs IO, such as when iterating over Entity Framework entities (materialized from a
DbReader) and Service Fabric Reliable Collections (which may require disk IO if the items are not in memory). Both of these frameworks expose their own
IAsyncEnumerator<T>, which work as I just described.
Unfortunately, C# is lagging a bit behind. While it does support creating iterators using
yield return and async methods using
await, currently there’s no way to combine the two. Or is there? 🙂
A very nifty feature has been added to the latest Roslyn beta (2.0.0-beta4 – not yet released) – “arbitrary async returns“. This was added mainly to address some allocation optimizations when dealing with Tasks (which are reference types) by providing an awaitable value type that defers the creation of the task until absolutely necessary, called
ValueTask. But the compiler feature is much more flexible than that – it enables us to create custom “async method builder” classes that allow returning any1 type from an async method.
I realized this feature could be somewhat abused to create async iterators, as seen in the above example.
How does it work?
YieldReturnAwaitablewhich the extension method
YieldReturn()returns. This awaitable/awaiter just wraps the task’s awaiter, except for the
IsCompletedproperty. More on that later.
AsyncEnumerableTaskMethodBuilder<T>which allows the compiler to create the async state machine. It works differently from the task method builder, because when returning an enumerable, it can be invoked multiple times by calling
GetEnumerator(). Also, the async state machine’s
MoveNext()is not invoked automatically, but rather by the enumerator’s
- The state machine is started by calling
TaskContinuationSource<bool>is created to hold the return value of
- Each time there’s an
awaitin the method, the
AwaitOnCompletedgets called (unless it completes synchronously). If it’s our special
YieldReturnAwaiter, we stop executing and set the
MoveNext()task result to
true. We also fetch the value using the awaiter’s
GetResult()method. Otherwise (as in the
Task.Delay()in the example), we just hook up the continuation and let it continue until hitting the next “yield return”.
- The state machine is started by calling
There’s a small “type safety” issue – the compiler won’t stop us from using
YieldReturn() on any type in the method. But of course we only take values from yields that match the method’s return type.
Lastly, this is just a prototype. I’ll have to review it more thoroughly to make sure it’s thread safe. I’m also not sure if
ExecutionContext capturing was done correctly.
1 Somewhat inaccurate – the return type must have a static method called
CreateAsyncMethodBuilder, so you can’t extend types you don’t own.
Tags.NET 4 Animation AppFabric Async Axum Blog C# ClearType Cloud CLR CodeValue Concurrency Contests Deep Zoom Experiments Generics Google Ink Lectures Modeling Performance Personal Pivot Prism Programming Languages Prolog Reflector ReSharper Roslyn RoslynPad Silverlight Themes Threading Tips WCF Windows 7 Windows 2003 Windows Azure Windows Forms Windows Phone Windows Vista WPF WPF Contrib XAML Zune
- August 2016
- March 2016
- February 2016
- December 2015
- July 2015
- April 2015
- September 2014
- April 2014
- March 2014
- July 2013
- May 2013
- February 2013
- June 2012
- May 2012
- June 2011
- November 2010
- August 2010
- July 2010
- June 2010
- March 2010
- December 2009
- November 2009
- February 2009
- January 2009
- December 2008
- November 2008
- October 2008
- September 2008
- August 2008
- November 2007
- September 2007
- May 2007
- February 2007
- November 2006
- October 2006
- February 2006
- August 2005
- August 2004
- July 2004
- June 2004
- May 2004