Eduasync part 4: Filling in AsyncTaskMethodBuilder<T>
In part 2, I introduced AsyncVoidMethodBuilder, AsyncTaskMethodBuilder and AsyncTaskMethodBuilder<T>. I showed all the signatures, but the implementations were basically trivial and non-functional. Today, I'm going to change all of that... at least a bit.
In particular, by the end of this post we'll be able to make the following code actually print out 10 as we'd expect:
private static void Main(string[] args)
{
Task<int> task = Return10Async();
Console.WriteLine(task.Result);
}
private static async Task<int> Return10Async()
{
return 10;
}
Note that there are no await statements - remember that AsyncTaskMethodBuilder<T> is about the boundary between the caller and the async method. We won't be actually doing anything genuinely asynchronous today - just filling in a piece of the infrastructure.
I'm not going to implement AsyncVoidMethodBuilder or AsyncTaskMethodBuilder, because once you understand how AsyncTaskMethodBuilder<T> works, the others are basically trivial to implement. (Certainly the non-generic AsyncTaskMethodBuilder is too similar to warrant going into; I may potentially revisit AsyncVoidMethodBuilder to investigate its behaviour on exceptions.)
Just as a reminder, this is what we need our AsyncTaskMethodBuilder<T> struct to look like in terms of its interface:
public struct AsyncTaskMethodBuilder<T>
{
public static AsyncTaskMethodBuilder<T> Create();
public void SetException(Exception e);
public void SetResult(T result);
public Task<T> Task { get; }
}
Frankly, this would be a bit of a pain to implement ourselves... especially if we wanted to do it in a neat way which didn't introduce a new thread unnecessarily. Fortunately, Parallel Extensions makes it pretty easy.
TaskCompletionSource to the rescue!
The TaskCompletionSource<TResult> type in the framework makes it almost trivial to implement AsyncTaskMethodBuilder<T>. It provides us everything we need - a task, and the ability to specify how it completes, either with an exception or a value. Our initial implementation is really just going to wrap TaskCompletionSource. I suspect the real CTP does slightly more work, but you can get a remarkably long way with simple wrapping:
public struct AsyncTaskMethodBuilder<T>
{
private readonly TaskCompletionSource<T> source;
private AsyncTaskMethodBuilder(TaskCompletionSource<T> source)
{
this.source = source;
}
public static AsyncTaskMethodBuilder<T> Create()
{
return new AsyncTaskMethodBuilder<T>(new TaskCompletionSource<T>());
}
public void SetException(Exception e)
{
source.SetException(e);
}
public void SetResult(T result)
{
source.SetResult(result);
}
public Task<T> Task { get { return source.Task; } }
}
We'll see how far that takes us - we may need to tweak it a bit, probably around the exception handling. For the moment though, it certainly does everything we need it to. The program runs, and prints out 10 as we'd expect.
This part of the code is now ready for asynchrony - even though nothing we've done so far actually creates any different threads. If we call SetResult from one thread while another thread is waiting on the task's result, the waiting thread will be unblocked as we'd expect.
Conclusion
In some ways I hope you're disappointed by this, in the same way that looking at the LINQ to Objects implementations can be slightly disappointing. It feels like so much is being given to us for free - the framework already had all this power, so what's C# 5 really adding to the mix. Well, we're nearly at the point where I can show you the details of what the compiler's doing under the hood. That's where the real power lies, and is why I'm so excited by the feature.
First though, we need to implement the awaiter pattern for Task<T>. That's the task of our next post, and then we can complete the picture by decompiling the generated code for some programs using genuine asynchrony.