How would you implement something that works similarly to the Async CTP await
keyword? Is there a simple implementation that works like await
in all cases, or does await
require different implementations for different scenarios?
The new await
keyword has similar semantics to the existing yield return
keyword in that they both cause the compiler to generate the continuation style state machine for you. So it is possible to hack something together using iterators that has some of the same behaviors as the Async CTP.
Here is what it would look like.
public class Form1 : Form
{
private void Button1_Click(object sender, EventArgs e)
{
AsyncHelper.Invoke<bool>(PerformOperation);
}
private IEnumerable<Task> PerformOperation(TaskCompletionSource<bool> tcs)
{
Button1.Enabled = false;
for (int i = 0; i < 10; i++)
{
textBox1.Text = "Before await " + Thread.CurrentThread.ManagedThreadId.ToString();
yield return SomeOperationAsync(); // Await
textBox1.Text = "After await " + Thread.CurrentThread.ManagedThreadId.ToString();
}
Button2.Enabled = true;
tcs.SetResult(true); // Return true
}
private Task SomeOperationAsync()
{
// Simulate an asynchronous operation.
return Task.Factory.StartNew(() => Thread.Sleep(1000));
}
}
Since yield return
generates an IEnumerable
our coroutine must return an IEnumerable
. All of the magic happens inside the AsyncHelper.Invoke
method. This is what gets our coroutine (masquerading as a hacked iterator) going. It takes special care to make sure the iterator is always executed on the current synchronization context if one exists which is important when trying to simulate how await
works on a UI thread. It does this by executing the first MoveNext
synchronously and then using SynchronizationContext.Send
to do the rest from a worker thread which is also used to asynchronously wait on the individual steps.
public static class AsyncHelper
{
public static Task<T> Invoke<T>(Func<TaskCompletionSource<T>, IEnumerable<Task>> method)
{
var context = SynchronizationContext.Current;
var tcs = new TaskCompletionSource<T>();
var steps = method(tcs);
var enumerator = steps.GetEnumerator();
bool more = enumerator.MoveNext();
Task.Factory.StartNew(
() =>
{
while (more)
{
enumerator.Current.Wait();
if (context != null)
{
context.Send(
state =>
{
more = enumerator.MoveNext();
}
, null);
}
else
{
enumerator.MoveNext();
}
}
}).ContinueWith(
(task) =>
{
if (!tcs.Task.IsCompleted)
{
tcs.SetResult(default(T));
}
});
return tcs.Task;
}
}
The whole bit about the TaskCompletionSource
was my attempt at replicating the way await
can "return" a value. The problem is that the coroutine has to actually return an IEnumerable
since it is nothing more than a hacked iterator. So I needed to come up with an alternate mechanism to capture a return value.
There are some glaring limitations with this, but I hope this gives you the general idea. It also demonstrates how the CLR could have one generalized mechanism for implementing coroutines for which await
and yield return
would use ubiquitously, but in different ways to provide their respective semantics.
await
always involves the same kind of transformation - but it's a pretty painful one. The library side of await
isn't too complicated, but the tricky bit is that the compiler builds a state machine for you, allowing the continuation to jump back to the right place.
It's possible that my some hacky use of iterator blocks (yield return) you could fake something similar... but it would be pretty ugly.
I gave a DevExpress webinar on what the compiler is doing behind the scenes a few weeks ago - that shows decompiled code from a couple of examples, as well as explaining how the compiler builds a task to return, and what the "awaiter" has to do. It may be useful to you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With