Using C# 2.0 iterators to simplify writing asynchronous code
A neat idea how C# 2.0 iterators can simplify the task of writing code that uses .NET async pattern.
I’ve recently attended Jeffrey Richter’s class dedicated to effective threading techniques, and that made me think about using the .NET async pattern. The typical usage of this pattern is the following: you start one or more asynchronous operations using obj.BeginOperation method (e.g. stream.BeginRead) passing a callback delegate and immediately return, releasing the thread that started processing. Once the async operation completes, your callback is called (usually on a thread-pool thread). The callback code checks what async operations have completed, and if you have enough data to proceed further it executes next block of code. This is very important for multithreaded servers, since very little resources are being used while the program waits for operation to finish (which can take a while).
But it is very hard to program with this pattern - instead of writing simple sequential code, the code is split into disconnected callbacks that may get executed in parallel. In some cases anonymous delegates make it somewhat better. But if more than one async operation is to be performed in sequence you usually need to maintain some state machine, evaluate where you are and what operation is to be performed next, etc - complex and buggy. Jeffry Richter showed some sample code doing this, but it is obvious it is very hard to write and maintain such code.
I thought it would be nice if there was a way to write sequential code that is put asleep (but does not waste thread), and then restarted when the async operation completes - so called coroutines (see https://en.wikipedia.org/wiki/Coroutines). Then I realized we already have these in C# 2.0 - in form of C# iterators! The idea is to organize the application code as an "iterator" that will "yield" some value after each async operation. Then we need a library utility class that restarts the iterator after the next async operation finishes.
The user code will looks like this:
IEnumerable<int> Foo(/* user params */...,
/* utility class */ AsyncEnumerator ae)
{
// start async operation
IAsyncResult asyncResult = stream.BeginRead(...,
/* callback provided by utility*/ ae.Advance,
null);
// we could do some more stuff here before yielding
yield return 1;
// the code below is called after the code above
// and after the asynchronous operation ends
int dataRead = stream.EndRead(asyncResult);
...
}
and is started by following code:
AsyncEnumerator ae = new AsyncEnumerator();
ae.Start(Foo(/* user params */ ..., ae));
The AsyncEnumerator utility guarantees the code is executed sequentially, although it may jump between threads :). My iterator code originally yield the IAsyncResult returned by async operation it just started, but it was not really used by utility class. I then showed it to Jeffrey Richter who suggested an ingenious idea - the iterator should produce the number of outstanding async operations started by the iterator code - and the utility class resumes the iterator when all of these operations have completed, this idea fits very well with the synchronization code.
Another issue solved by the utility is exception handling: C# iterators can't have catch block around yield statement, so centralized error handling is hard. The utility catches exceptions raised by iterator, and calls a user-provided delegate that performs exception handling and returns boolean value indicating whether the iteration should stop (another neat improvement by Jeffrey Richter).
If this made you interested, come back to this blog - I'll post details on the inner working of the utility in the next entry.
References:
C# 2.0 iterators https://msdn.microsoft.com/msdnmag/issues/04/05/C20/
Async method pattern in .NET
https://msdn.microsoft.com/msdnmag/issues/01/08/Async/
https://msdn.microsoft.com/msdnmag/issues/04/01/BasicInstincts/default.aspx
- Anonymous
March 30, 2006
Pretty interesting, please post the rest as well. Thanks. - Anonymous
March 30, 2006
Hello,
ingenius idea! And a great article.
But I could not wait for second part, so I came up with my own implementation. - Anonymous
April 01, 2006
Previous article describes the idea of using C# 2.0 iterators to write asynchronous code, now it's time... - Anonymous
April 01, 2006
My article on this idea:
http://msmvps.com/blogs/79813.aspx