Wednesday, May 28, 2008 #

Dangerous Ideas in C# No. 2: Yielding to the Inevitable

C# 2.0 brought us the new 'yield' keyword. In a method or property that has an IEnumerable return type, yield return allows us to procedurally generate the values that will be provided by the resulting enumerator - such a method or property being known as an iterator. The assumption behind most uses of yield return is generally that someone will take the IEnumerable C# gins up for us and stick it in a foreach loop (potentially, in a LINQ world, after applying some query operators that filter or transform our yielded values first).

But ultimately that foreach loop will just be calling GetEnumerator on our iterator IEnumerable, then repeatedly calling MoveNext and extracting the Current value from the resulting IEnumerator. There's no rule that says only foreach loops are allowed to do that.

What matters here is that the ultimate upshot of our writing a yielding IEnumerable is that each call to MoveNext executes a bit of the method we write; whenever we yield return (or yield break - or reach the end of the method, which is equivalent to a yield break), that call to MoveNext returns. It returns true if we issued a yield return, and false if we issued a yield break. (The magic state machine that the C# compiler generates for us being an implementation detail we can ignore while working at the abstraction level of the language. Under the covers, all kinds of crazy stuff may go on at runtime, but syntactically, it's useful to imagine that we're executing our method a bit at a time.)

Some important consequences of this. After you yield return, and a call to MoveNext returns true, there's no guarantee that the calling code will subsequently call MoveNext and execute the subsequent lines of code...

  • ... immediately
  • ... from the same calling method at the same point on the stack
  • ... on the same thread
  • ... or even at all

although all of these things will normally be the case if the calling code uses a foreach loop. If the calling foreach loop contains a break or return statement, though, for instance, your code won't be called back.

(It's worth noting that the enumerator C# produces for you will be IDisposable, and so there SHOULD be a subsequent call to Dispose() that will, if your iterator code has any finally or using blocks, ensure they are tidied up appropriately - yet more state machine magic sorts this out for you. But of course, if the calling code doesn't explicitly call Dispose(), you'll be waiting for the garbage collector to come along before that disposal takes place. Foreach automatically disposes the enumerator whenever - and however - the loop exits.)

One thing you ought to be able to be reasonably certain of is that if MoveNext is called again, the next part of your code will at least be executing in the same appdomain that the first part was, although - and this is a REALLY dangerous idea - it's possible to hack together a serialization mechanism that allows some iterators to be serialized mid-iteration, then 'thawed' in a completely different appdomain and continue running - so you can't even completely rely upon that.

Now, this is supposed to be a post about dangerous ideas in C#, so obviously the question arises, if you can abuse an iterator by calling MoveNext on, say, different threads, does that let us do anything cool? I think the answer is yes, but just because we can doesn't mean we should. In production code, at least. Here, we can play...

For the purposes of my example, I'm going to introduce the TinyURL API. TinyURL takes long URLs, sticks them in a database for you, and hands you back a shorter URL that you can use in places where lengthy URLs might be awkward, such as Twitter. This short URL redirects people who access it on to the URL you originally supplied. Simple.

If you are writing an application (perhaps a twitter client) that needs to be able to generate tiny URLs on demand, you might need to access TinyURL's API. It's not terribly complicated, as APIs go. To get a tiny URL for a URL, X, you simply send an HTTP GET request to http://tinyurl.com/api-create.php?url=X. No URL encoding is normally needed (since you're passing a URL). The response body will be a plain text string containing the tiny URL.

You can get a tiny URL, then, from .NET code with a one-liner:

string tinyUrl = new WebClient()
    .DownloadString("http://tinyurl.com/api-create.php?url=" + url);

That seems far too simple to me. This is .NET, not PHP. Let's see how much we can complicate matters, ostensibly with the aim of making the above call asynchronous. For starters, let's use System.Net.WebRequest instead of System.Net.WebClient. First of all, still synchronously:

public string GetTinyUrl(string url)
{
    WebRequest req = WebRequest.Create(GetTinyUrlApiUrl(url));
    WebResponse response = req.GetResponse();
    return StringFromResponse(response);
}
private string StringFromResponse(WebResponse response)
{
    return new StreamReader(response.GetResponseStream()).ReadToEnd();
}
private string GetTinyUrlApiUrl(string url)
{
    return "http://tinyurl.com/api-create.php?url=" + url;
}

That's a start - we've used a factory method, so our need to use at least one GOF pattern in our solution is met, and we can feel slightly superior to PHP programmers as a result. But what we've ended up with looks like something a Java programmer might be comfortable with. We can't be having that. What happens when we go asynchronous?

public void GetTinyUrlAsync(string url, Action<string> handleTinyUrl)
{
    WebRequest req = WebRequest.Create(GetTinyUrlApiUrl(url));
    var handler = new Handler(req, handleTinyUrl);
    req.BeginGetResponse(handler.HandleResponse, handleTinyUrl);
}

private class Handler
{
    public Handler(WebRequest request, Action<string> handleTinyUrlCallback)
    {
        Request = request;
        HandleTinyUrlCallback = handleTinyUrlCallback;
    }

    public WebRequest Request { get; private set; }
    public Action<string> HandleTinyUrlCallback { get; private set; }

    public void HandleResponse(IAsyncResult result)
    {
        WebResponse response = Request.EndGetResponse(result);
        string resultString = StringFromResponse(response);
        HandleTinyUrlCallback(resultString);
    }
}

Goodness - it looks even more like Java now with that inner class bloating things. And wow - there's 16 lines of code between the call to BeginGetResponse and EndGetResponse, all of which is concerned with the messy business of preserving state between them. Thank goodness we can do better; a C# 2 closure will manage all that messy state for us:

public void GetTinyUrlAsync(string url, Action<string> handleTinyUrl)
{
    WebRequest req = WebRequest.Create(GetTinyUrlApiUrl(url));
    req.BeginGetResponse(delegate(IAsyncResult result)
    {
        WebResponse response = req.EndGetResponse(result);
        string resultString = StringFromResponse(response);
        handleTinyUrl(resultString);
    }
    , null);
}

Well, we wanted to get the Begin and End calls closer together - now the call to EndGetResponse is actually inside the call to BeginGetResponse. Not sure about you, but that feels a little bit too close to me. It's hard enough to follow asynchronous logic without it all being piled up in a single statement.

Using a slightly different approach, we can preserve a slightly better sequential ordering, but the code isn't getting any prettier overall:

public void GetTinyUrlAsync(string url, Action<string> handleTinyUrl)
{
    WebRequest req = WebRequest.Create(GetTinyUrlApiUrl(url));

    IAsyncResult result = req.BeginGetResponse(null, null);

    ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, 
    (state, timedOut) => {
        WebResponse response = req.EndGetResponse(result);
        string resultString = StringFromResponse(response);
        handleTinyUrl(resultString);
    }
    , null, -1, true);
}

It would be nice to get rid of that anonymous delegate altogether, wouldn't it? And weren't we promised yield statements? Where are the yield statements? Okay, I'm going to cut straight to the chase now and show you this week's dangerous idea:

public IEnumerable<IAsyncResult> GetTinyUrlAsync(string url, Action<string> handleTinyUrl)
{
    WebRequest req = WebRequest.Create(GetTinyUrlApiUrl(url));
    IAsyncResult result = req.BeginGetResponse(null, null);

    while (!result.IsCompleted)
    {
        yield return result;
    }

    WebResponse response = req.EndGetResponse(result);
    string resultString = StringFromResponse(response);

    handleTinyUrl(resultString);
}

Now, that does look a lot tidier - it concentrates the logic on the web request, not on the threading (See why I made you read all the ugly code? It makes this look almost readable by comparison). It actually leaves the threading decision up to the caller. Want to run it synchronously? Here's how:

string tinyUrl;

IEnumerable<IAsyncResult> iterator = 
    GetTinyUrlAsync("http://blogs.ipona.com/james", str => tinyUrl = str);
foreach (IAsyncResult res in yieldingRetriever) { };

Okay, how did that work? Well, essentially, it sets things up, then the foreach loop at the end runs the whole method, filling the while loop in the middle with a no-op. The upshot is that the thread spins until the async result returns. (you might want to put a Thread.Sleep in the foreach body to stop the loop hogging the CPU).

Now let's see how to run it asynchronously:

IEnumerator<IAsyncResult> enumerator = iterator.GetEnumerator();
WaitOrTimerCallback throwAsyncResultToTheThreadPool = null;
throwAsyncResultToTheThreadPool = delegate
{
    // Looking to see if we've got another async result to handle
    if (enumerator.MoveNext())
    {
        // Scheduling op to complete on threadpool thread
        ThreadPool.RegisterWaitForSingleObject(enumerator.Current.AsyncWaitHandle, 
throwAsyncResultToTheThreadPool, null, -1, true); } }; throwAsyncResultToTheThreadPool(null, false);

See why it was a dangerous idea? It leads to messes like this semi-recursive anonymous method from hell (and think how much worse it would look with the error handling left in - notice no call here to enumerator.Dispose(), for instance). But it's actually quite neat - whenever the iterator yields an IAsyncResult, we let the threadpool deal with waiting for it to return. For I/O operations, that won't block a threadpool thread until there's actually data to handle, which is pretty tidy. When the IAsyncResult's WaitHandle releases, the next part of the iterator is executed on an available threadpool thread. If that results in another IAsyncResult, we let the threadpool wait for that too; and so on until there are no more IAsyncResults - in other words, until the method completes.

Edited 29th May 2008 to add:

Now, I want to emphasize the really cool effect this has. The first half of the method runs on the original calling thread. The second half of the method, after the yield return, runs on a threadpool thread - and not until the web request has returned. Let me repeat that. The second half of the method runs on a different thread. And yet it uses the same local variables, the same parameters, the same everything as the first half of the method.

This is an example of what a computer sciency person would call a 'continuation', by the way. C# yield return statements make it dead easy to stop execution of a method, take the enumerator that represents the state of the method at that point (that's the 'continuation'), pass it around in our code until we want to carry on running it, and then execute the next bit, with all the same local variables and data as we had before.

Now, you're not going to want to write code like that to call the iterator every time. But the beauty of it is that these strategies can be encapsulated in completely generic methods:

public TReturn RunIteratorSynchronously<TArg, TReturn>(Func<TArg, Action<TReturn>, 
IEnumerable<IAsyncResult>> iteratorGenerator, TArg arg) { TReturn returnValue = default(TReturn); IEnumerable<IAsyncResult> iterator = iteratorGenerator(arg, val => returnValue = val); foreach (IAsyncResult res in iterator) { Thread.Sleep(1); }; return returnValue; } public void RunIteratorAsynchronously<TArg, TReturn>(Func<TArg, Action<TReturn>,
IEnumerable<IAsyncResult>> iteratorGenerator, TArg arg, Action<TReturn> callback) { IEnumerable<IAsyncResult> iterator = iteratorGenerator(arg, callback); IEnumerator<IAsyncResult> enumerator = iterator.GetEnumerator(); WaitOrTimerCallback throwAsyncResultToTheThreadPool = null; throwAsyncResultToTheThreadPool = delegate { // Looking to see if we've got another async result to handle if (enumerator.MoveNext()) { // Scheduling op to complete on threadpool thread ThreadPool.RegisterWaitForSingleObject(enumerator.Current.AsyncWaitHandle,
throwAsyncResultToTheThreadPool, null, -1, true); } }; throwAsyncResultToTheThreadPool(null, false); }

So I can now call the IAsyncResult-yielding TinyUrl iterator with either of these approaches as follows:

Console.WriteLine(RunIteratorSynchronously<string, string>(GetTinyUrlAsync, 
    "http://blogs.ipona.com/james"));

RunIteratorAsynchronously<string, string>(GetTinyUrlAsync, 
    "http://blogs.ipona.com/james", Console.WriteLine);

 

Edited 29th May 2008 to add:

Also, worth thinking about what other execution strategies you could try. How about a scheduler that handles a number of these iterators all on one thread, by looping through them calling MoveNext() on each one in turn? That would basically be an implementation of a co-operative 'fiber' multitasking system...

And it's when you end up with something as tidy as that that it becomes clear just how dangerous an idea it was in the first place, because you end up being tempted to justify it...

 

 Remember, James Hart is a professional stunt programmer. Do not try this on a production codebase.

posted @ Wednesday, May 28, 2008 9:22 PM | Feedback (2)