You’ve all read about the asynchrony promise of C# 5 (if you haven’t, I highly recommend reading Eric Lippert’s series about the subject or this post won’t make much sense). I think it’s a great step forward, and it would make asynchronous programming all a lot easier.

We already know how to think in Tasks instead of threads, .NET 4.0 taught us that.

We already know how to use continuations (or at least some weak form of it), C# 2.0 iterators taught us that.

 

So, as an experiment*, I went ahead and implemented a weak form of the await/async magic** using “the materials in the room”, i.e. tasks and iterators – minus the syntactic sugar, of course. Let’s have a look.

 

First, A Sample

In C# 5 we would have (taken from Anders’ Netflix sample):

  1. async void LoadMoviesAsync(int year)
  2. {
  3.     while (true)
  4.     {
  5.         var movies = await QueryMoviesAsync(year, imageCount, pageSize, cts.Token);
  6.         if (movies.Length == 0) break;
  7.         DisplayMovies(movies);
  8.     }
  9. }

While in my implementation it would look like this:

  1. IEnumerable<Task> LoadMovies(int year)
  2. {
  3.     while (true)
  4.     {
  5.         var moviesTask = Async.Await<Movie[]>(result =>
  6.             QueryMovies(result, year, imageCount, pageSize, cts.Token));
  7.         yield return moviesTask;
  8.         var movies = moviesTask.Result;
  9.         if (movies.Length == 0) break;
  10.         DisplayMovies(movies);
  11.     }
  12. }

What’s going on here?

First of all, as you can see, the methods look very similar. We have created an iterator that allows us to start running the method and break after every yield. The implementation of Async.Await() method is surprisingly simple:

  • The Await() method simply creates an enumerator, which starts up the state machine.
  • It calls MoveNext(), which executes the method up to the next yield.
  • We get a Task from the yield, and attach a continuation to it, which calls MoveNext() again, and so on.

 

  1. public static Task<T> Await<T>(Func<TaskCompletionSource<T>, IEnumerable<Task>> func)
  2. {
  3.     var completionSource = new TaskCompletionSource<T>();
  4.     Await(func(completionSource));
  5.     return completionSource.Task;
  6. }
  7.  
  8. public static void Await(IEnumerable<Task> iterator)
  9. {
  10.     IEnumerator<Task> enumerator = iterator.GetEnumerator();
  11.     Run(enumerator);
  12. }
  13.  
  14. private static void Run(IEnumerator<Task> enumerator)
  15. {
  16.     if (enumerator.MoveNext())
  17.     {
  18.         enumerator.Current.ContinueWith(t => Run(enumerator));
  19.     }
  20. }

Why do we need TaskCompletionSource?

The return value of the iterator method must always be IEnumerable<Task>. But what if we want to return a value from a method? Remember, it doesn’t execute synchronously anymore! That is why methods that (originally) do not return void, have the option of adding an argument of TaskCompletionSource. In the method, we can call TaskCompletionSource.SetResult() to set the result. We also return the Task the TCS creates, so the caller could access the result. The overload of Await() that we use in this case simply wraps around this functionality, and enables a more concise syntax.

When an asynchronous method has no return value, TaskCompletionSource is not needed.

 

What’s missing?

In the next installment(s) we will discuss:

  • Task Schedulers (how to make sure we’re in the right context for UI operations)
  • Limited exception handling

 

* Yes, it means I wouldn’t recommend using this in “real” code – just wait for C# 5. This is just for fun.

** And I use the term very figuratively.

Tagged with:
 

Comments are closed.

Set your Twitter account name in your settings to use the TwitterBar Section.