Posted April 24, 201212 yr FPCH Admin The recent blog post await[/b] keyword in C# and Visual Basic enables developers to use WinRT asynchronous operations while still maintaining and reasoning about good control flow. In this follow-on post, I dive much deeper into exactly how await works with WinRT. This knowledge will make it easier for you to reason about code that uses await, and as a result, will enable you to write better Metro style apps. To start, let’s ground ourselves by taking a look at a world without await. Reviewing the basics All of asynchrony in WinRT is rooted in a single interface: IAsyncInfo. public interface IAsyncInfo { AsyncStatus Status { get } HResult ErrorCode { get } uint Id { get } void Cancel() void Close() } Every asynchronous operation in WinRT implements this interface, which provides the base functionality necessary to walk up to an asynchronous operation and inquire about its identity and status, and to request its cancellation. But this particular interface lacks what’s arguably the most important aspect of an asynchronous operation: a callback to notify a listener when the operation has completed. That capability is purposefully separated out into four other interfaces that all require IAsyncInfo, and every asynchronous operation in WinRT implements one of these four interfaces: public interface IAsyncAction : IAsyncInfo { AsyncActionCompletedHandler Completed { get set } void GetResults() } public interface IAsyncOperation : IAsyncInfo { AsyncOperationCompletedHandler Completed { get set } TResult GetResults() } public interface IAsyncActionWithProgress : IAsyncInfo { AsyncActionWithProgressCompletedHandler Completed { get set } AsyncActionProgressHandler Progress { get set } void GetResults() } public interface IAsyncOperationWithProgress : IAsyncInfo { AsyncOperationWithProgressCompletedHandler Completed { get set } AsyncOperationProgressHandler Progress { get set } TResult GetResults() } These four interfaces support all combinations of with-and-without results, and with-and-without progress reporting. All the interfaces expose a Completed property, which can be set to a delegate that is invoked when the operation completes. You may set the delegate only once, and if it’s set after the operation has already completed, it is immediately scheduled or invoked, with the implementation handling the race between the operation completing and the delegate being assigned. Now, let’s say I wanted to implement a Metro style app with a XAML Button, such that clicking the button queues some work to the WinRT thread pool to perform a computationally-intensive operation. When that work completes, the content of the button is updated with the result of the operation. How might we implement this? The WinRT ThreadPool class exposes a method to asynchronously run work on the pool: public static IAsyncAction RunAsync(WorkItemHandler handler) We can use this method to queue our computationally-intensive work so as to avoid blocking our UI thread during its run: private void btnDoWork_Click(object sender, RoutedEventArgs e) { int result = 0 var op = ThreadPool.RunAsync(delegate { result = Compute() }) } We now successfully offloaded the work from the UI thread to the pool, but how do we know when the work is done? RunAsync returns an IAsyncAction, so we can use a completion handler to receive that notification and run our continuation in response: private void btnDoWork_Click(object sender, RoutedEventArgs e) { int result = 0 var op = ThreadPool.RunAsync(delegate { result = Compute() }) op.Completed = delegate(IAsyncAction asyncAction, AsyncStatus asyncStatus) { btnDoWork.Content = result.ToString() // bug! } } Now when the asynchronous operation queued to the ThreadPool completes, our Completed handler is invoked and tries to store the result into our button. Unfortunately, this is currently broken. The Completed handler is unlikely to be invoked on the UI thread, and yet, to modify btnDoWork.Content, the handler needs to be running on the UI thread (if it doesn’t, an exception with the error code RPC_E_WRONG_THREAD will result). To deal with this, we can use the CoreDispatcher object associated with our UI to marshal the invocation back to where we need it to be: private void btnDoWork_Click(object sender, RoutedEventArgs e) { int result = 0 var op = ThreadPool.RunAsync(delegate { result = Compute() }) op.Completed = delegate(IAsyncAction asyncAction, AsyncStatus asyncStatus) { Dispatcher.RunAsync(CoreDispatcherPriority.Normal, delegate { btnDoWork.Content = result.ToString() }) } } This is now functional. But what if the Compute method throws an exception? Or what if someone calls Cancel on the IAsyncAction returned from ThreadPool.RunAsync? Our Completed handler needs to deal with the fact that the IAsyncAction may end in one of three terminal states: Completed, Error, or Canceled: private void btnDoWork_Click(object sender, RoutedEventArgs e) { int result = 0 var op = ThreadPool.RunAsync(delegate { result = Compute() }) op.Completed = delegate(IAsyncAction asyncAction, AsyncStatus asyncStatus) { Dispatcher.RunAsync(CoreDispatcherPriority.Normal, delegate { switch (asyncStatus) { case AsyncStatus.Completed: btnDoWork.Content = result.ToString() break case AsyncStatus.Error: btnDoWork.Content = asyncAction.ErrorCode.Message break case AsyncStatus.Canceled: btnDoWork.Content = "A task was canceled" break } }) } } That’s a fair amount of code to write to handle a single asynchronous invocation imagine what it would look like if we needed to perform many asynchronous operations in sequence? Wouldn’t it be nice if we could instead write code like this? private async void btnDoWork_Click(object sender, RoutedEventArgs e) { try { int result = 0 await ThreadPool.RunAsync(delegate { result = Compute() }) btnDoWork.Content = result.ToString() } catch (Exception exc) { btnDoWork.Content = exc.Message } } This code behaves exactly like the previous code. But we don’t need to deal manually with completion callbacks. We don’t need to marshal back manually to the UI thread. We don’t need to explicitly check completion status. And we don’t need to invert our control flow, which means this is now trivial to extend with more operations, e.g. to do multiple computations and UI updates in a loop: private async void btnDoWork_Click(object sender, RoutedEventArgs e) { try { for(int i=0 i { int result = 0 await ThreadPool.RunAsync(delegate { result = Compute() }) btnDoWork.Content = result.ToString() } } catch (Exception exc) { btnDoWork.Content = exc.Message } } Take just a moment to think through the code that you would have had to write to achieve that using the IAsyncAction manually. This is the magic of the new async/await keywords in C# and Visual Basic. The good news is that you can in fact write this exact code, and it’s not actually magic. Through the rest of this post, we explore exactly how this works behind the scenes. Compiler transformations Marking a method with the async keyword causes the C# or Visual Basic compiler to rewrite the method’s implementation using a state machine. Using this state machine the compiler can insert points into the method at which the method can suspend and resume its execution without blocking a thread. These points aren’t inserted haphazardly. They’re inserted only where you explicitly use the await keyword: private async void btnDoWork_Click(object sender, RoutedEventArgs e) { ... await something // ... } When you await an asynchronous operation that’s not yet completed, the compiler’s generated code ensures that all of the state associated with the method (e.g. local variables) is packaged up and preserved on the heap. Then the function returns to the caller, allowing the thread on which it was running to do other work. When the awaited asynchronous operation later completes, the method’s execution resumes using the preserved state. Any type that exposes the await pattern can be awaited. The pattern consists primarily of exposing a GetAwaiter method that returns a type that provides IsCompleted, OnCompleted, and GetResult members. When you write: await something the compiler generates code that uses these members on the instance something to check whether the object is already completed (via IsCompleted), and if it’s not completed, to hook up a continuation (via OnCompleted) that calls back to continue execution when the task eventually completes. After the operation is completed, any exceptions from the operation are propagated and/or a result returned (via GetResult). So, when you write this: private async void btnDoWork_Click(object sender, RoutedEventArgs e) { ... await ThreadPool.RunAsync(delegate { result = Compute() }) ... } the compiler translates that into code similar to this: private class btnDoWork_ClickStateMachine : IAsyncStateMachine { // Member fields for preserving locals and other necessary state int $state TaskAwaiter $awaiter int result ... // Method that moves to the next state in the state machine public void MoveNext() { // Jump table to get back to the right statement upon resumption switch (this.$state) { ... case 2: goto Label2 ... } ... // Expansion of await ... var tmp1 = ThreadPool.RunAsync(delegate { this.result = Compute() }) this.$awaiter = tmp1.GetAwaiter() if (!this.$awaiter.IsCompleted) { this.$state = 2 this.$awaiter.OnCompleted(MoveNext) return Label2: } this.$awaiter.GetResult() ... } } For the method btnDoWork_Click, the compiler generates a state machine class that contains a MoveNext method. Every call to MoveNext resumes the execution of the btnDoWork_Click method until it reaches the next await on something that’s not yet completed, or until the end of the method, whichever comes first. When the compiler-generated code finds an awaited instance that’s not yet completed, it marks the current location with a state variable, schedules execution of the method to continue when the awaited instance completes, and then returns. When the awaited instance eventually completes, the MoveNext method is invoked again and jumps to the point in the method where execution previously left off. The compiler doesn’t actually care that an IAsyncAction is being awaited here. All it cares about is that the right pattern is available with which to bind. Of course, you’ve seen what the IAsyncAction interface looks like, and you’ve seen that it doesn’t contain a GetAwaiter method like that expected by the compiler. So how is it that this successfully compiles and runs? To best understand that, you first need to understand the .NET Task and Task types (the Framework’s core representation of asynchronous operations), and how they relate to await. Converting to tasks The .NET Framework 4.5 includes all the types and methods necessary to support awaiting Task and Task instances (Task derives from Task). Task and Task both expose GetAwaiter instance methods, which respectively return TaskAwaiter and TaskAwaiter types that expose the necessary IsCompleted, OnCompleted, and GetResult members sufficient to satisfy the C# and Visual Basic compilers. IsCompleted returns a Boolean indicating whether the task is done executing as of the moment the property is accessed. OnCompleted hooks up to the task a continuation delegate that is invoked when the task completes (if the task is already completed when OnCompleted is invoked, the continuation delegate is scheduled for execution asynchronously). GetResult returns the result of the task if it ended in the TaskStatus.RanToCompletion state (it returns void for the non-generic Task type), throws an OperationCanceledException if the task ended in the TaskStatus.Canceled state, and throws whatever exception caused the task to fail if the task ended in the TaskStatus.Faulted state. If we have a custom type that we want to support awaiting, we have two primary options. One option is to implement the whole await pattern manually for our custom awaitable type, providing a GetAwaiter method that returns a custom awaiter type that knows how to deal with continuations and exception propagation and the like. The second is to implement the ability to convert from our custom type to a task, and then just rely on the built-in support for awaiting tasks to await our special type. Let’s explore this latter approach. The .NET Framework includes a type called TaskCompletionSource, which makes these kinds of conversions straightforward. TaskCompletionSource creates a Task object and gives you SetResult, SetException, and SetCanceled methods that you use to directly control when and in what state the corresponding task completes. So, you can use a TaskCompletionSource as a kind of shim or proxy to represent some other asynchronous operation, such as a WinRT asynchronous operation. Let’s pretend for a moment that you didn’t already know you could directly await WinRT operations. How then could you enable doing so? IAsyncOperation op = SomeMethodAsync() string result = await ... // need something to await You could create a TaskCompletionSource, use that as a proxy to represent the WinRT async operation, and then await the corresponding task. Let’s try. First, we need to instantiate a TaskCompletionSource such that we can await its Task: IAsyncOperationstring> op = SomeMethodAsync() var tcs = new TaskCompletionSource() ... string result = await tcs.Task Then, just as we saw in our earlier example of manually using WinRT async operations’ Completed handlers, we need to hook up a callback to the async operation so that we know when it completes: IAsyncOperation op = SomeMethodAsync()var tcs = new TaskCompletionSource()op.Completed = delegate { ... } string result = await tcs.Task And then in that callback, we need to transfer the IAsyncOperation’s completion state over to the task: IAsyncOperationstring> op = SomeMethodAsync() var tcs = new TaskCompletionSource() op.Completed = delegate { switch(operation.Status) { AsyncStatus.Completed: tcs.SetResult(operation.GetResults()) break AsyncStatus.Error: tcs.SetException(operation.ErrorCode) break AsyncStatus.Canceled: tcs.SetCanceled() break } } string result = await tcs.Task That’s it. WinRT async operations ensure that the Completed handler is invoked appropriately even if the handler is signed up after the operation has already completed, so we don’t need to do anything special for signing up the handler racing with the operation’s completion. WinRT async operations also take care to drop the reference to the Completed handler after the operation has completed, so we don’t need to do anything special to set Completed to null when our handler is invoked in fact, the Completed handler is set-once, meaning that after you set it, you get an error if you try to set it again. With this approach, there’s a one-to-one mapping between how the state in which the WinRT async operation completes and the state in which the representing task completes: Terminal AsyncStatus Converts to TaskStatus Which when awaited... Completed RanToCompletion Returns the operation’s result (or void) Error Faulted Throws the failed operation’s exception Canceled Canceled Throws an OperationCanceledException Of course, the boilerplate code we wrote to handle this one await would become tedious very quickly if we had to write it every time we wanted to await a WinRT asynchronous operation. As good programmers, we can encapsulate that boilerplate into a method we can use over and over again. Let’s do so as an extension method that converts the WinRT async operation into a task: public static Task AsTask( this IAsyncOperation operation) { var tcs = new TaskCompletionSource() operation.Completed = delegate { switch(operation.Status) { AsyncStatus.Completed: tcs.SetResult(operation.GetResults()) break AsyncStatus.Error: tcs.SetException(operation.ErrorCode) break AsyncStatus.Canceled: tcs.SetCanceled() break } } return tcs.Task } With that extension method, I can now write code like: IAsyncOperation op = SomeMethodAsync() string result = await op.AsTask() or even more simply as: string result = await SomeMethodAsync().AsTask() Much better. Of course, this kind of cast-like AsTask functionality will be in hot demand by anyone using WinRT from C# and Visual Basic, so you don’t actually need to write your own implementation: there are already such methods built into .NET 4.5. The System.Runtime.WindowsRuntime.dll assembly contains these extension methods for the WinRT async interfaces: namespace System { public static class WindowsRuntimeSystemExtensions { // IAsyncAction public static Task AsTask( this IAsyncAction source) public static Task AsTask( this IAsyncAction source, CancellationToken cancellationToken) // IAsyncActionWithProgress public static Task AsTask( this IAsyncActionWithProgress source) public static Task AsTask( this IAsyncActionWithProgress source, IProgress progress) public static Task AsTask( this IAsyncActionWithProgress source, CancellationToken cancellationToken) public static Task AsTask( this IAsyncActionWithProgress source, CancellationToken cancellationToken, IProgress progress) // IAsyncOperation public static Task AsTask( this IAsyncOperation source) public static Task AsTask( this IAsyncOperation source, CancellationToken cancellationToken) // IAsyncOperationWithProgress public static Task AsTask( this IAsyncOperationWithProgress source) public static Task AsTask( this IAsyncOperationWithProgress source, IProgress progress) public static Task AsTask( this IAsyncOperationWithProgress source, CancellationToken cancellationToken) public static Task AsTask( this IAsyncOperationWithProgress source, CancellationToken cancellationToken, IProgress progress) ... } } Each of the four interfaces has a parameterless AsTask overload similar to the one we just wrote from scratch. In addition, each also has an overload that accepts a CancellationToken. This token is the common mechanism in .NET used to provide composable and cooperative cancellation you pass a token into all of your asynchronous operations, and when cancellation is requested, all of those async operations will have cancellation requested. Just for illustration (because as you now know such an API is already available), how might we build our own such AsTask(CancellationToken) overload? CancellationToken provides a Register method that accepts a delegate to be invoked when cancellation is requested we can simply provide a delegate then that calls Cancel on the IAsyncInfo object, forwarding along the cancellation request: public static Task AsTask( this IAsyncOperation operation, CancellationToken cancellationToken { using(cancellationToken.Register(() => operation.Cancel())) return await operation.AsTask() } Although the implementation that ships in .NET 4.5 isn’t exactly like this, logically it’s the same. For IAsyncActionWithProgress and IAsyncOperationWithProgress, there are also overloads that accept an IProgress. IProgress is a .NET interface that methods can accept to report back progress, and the AsTask method simply wires up a delegate for the WinRT async operation’s Progress property so that it forwards the progress info to the IProgress. Again, just as an example for how this might be implemented manually: public static Task AsTask( this IAsyncOperationWithProgress operation, IProgress progress { operation.Progress += (_,p) => progress.Report(p) return operation.AsTask() } Directly awaiting WinRT async We’ve now seen how it’s possible to create tasks to represent WinRT async operation such that we can then await those tasks. But what about directly awaiting the WinRT operations? In other words, it’s fine to be able to write: await SomeMethodAsync().AsTask() but for cases where we don’t need to supply a CancellationToken or an IProgress, wouldn’t it be nice to avoid coding the call to AsTask at all? await SomeMethodAsync() Of course, this is possible, as we have seen at the beginning of this post. Remember how the compiler expects to find a GetAwaiter method that returns an appropriate awaiter type? The aforementioned WindowsRuntimeSystemExtensions type in System.Runtime.WindowsRuntime.dll includes just such GetAwaiter extension methods for the four WinRT async interfaces: namespace System { public static class WindowsRuntimeSystemExtensions { ... public static TaskAwaiter GetAwaiter( this IAsyncAction source) public static TaskAwaiter GetAwaiter( this IAsyncOperation source) public static TaskAwaiter GetAwaiter( this IAsyncActionWithProgress source) public static TaskAwaiter GetAwaiter( this IAsyncOperationWithProgress source) } } Note the return type from each of these methods: TaskAwaiter or TaskAwaiter. Each of these methods is taking advantage of the existing task awaiters built into the Framework. Knowing what you now know about AsTask, you can probably guess how these are implemented. The real implementation in the Framework is almost exactly like this: public static TaskAwaiter GetAwaiter( this IAsyncAction source) { return source.AsTask().GetAwaiter() } This means that these two lines both result in exactly the same behavior: await SomeMethodAsync().AsTask() await SomeMethodAsync() Customizing await behavior As mentioned previously, TaskAwaiter and TaskAwaiter supply all of the members necessary to meet the compiler’s expectation of an awaiter: bool IsCompleted { get } void OnCompleted(Action continuation) TResult GetResult() //returns void on TaskAwaiter The most interesting member here is OnCompleted, as it’s the one responsible for invoking the continuation delegate when the awaited operation completes. OnCompleted provides special marshaling behavior to ensure that the continuation delegate is executed in the right place. By default, when the task’s awaiter’s OnCompleted is called, it notes the current SynchronizationContext, which is an abstract representation of the environment in which the code is executing. On the UI thread of a Metro style app, SynchronizationContext.Current returns an instance of the internal WinRTSynchronizationContext type. SynchronizationContext provides a virtual Post method that accepts a delegate and executes that delegate in an appropriate place for the context WinRTSynchronizationContext wraps a CoreDispatcher and uses its RunAsync to invoke the delegate back on the UI thread asynchronously (just as we manually did earlier in this post). When the awaited task completes, the delegate passed to OnCompleted is Post’ed for execution to the captured SynchronizationContext that was current when OnCompleted was invoked. This is what allows you to write code using await in your UI logic without worrying about marshaling back to the right thread: task’s awaiter is handling it for you. Of course, there may be situations in which you don’t want this default marshaling behavior. Such situations occur frequently in libraries: many kinds of libraries don’t care about manipulating UI controls or the particular threads on which they execute, and thus from a performance perspective, it’s helpful to be able to avoid the overhead associated with cross-thread marshaling. To accommodate code that wants to disable this default marshaling behavior, Task and Task provide ConfigureAwait methods. ConfigureAwait accepts a Boolean continueOnCapturedContext parameter: passing true means to use the default behavior, and passing false means that the system doesn’t need to forcefully marshal the delegate’s invocation back to the original context and can instead execute the delegate wherever the system sees fit. Given that, if you want to await a WinRT operation without forcing the rest of the execution back to the UI thread, instead of writing either: await SomeMethodAsync() or: await SomeMethodAsync().AsTask() you can write: await SomeMethodAsync().AsTask() .ConfigureAwait(continueOnCapturedContext:false) or just: await SomeMethodAsync().AsTask().ConfigureAwait(false) When to use AsTask If all you want to do is invoke a WinRT asynchronous operation and wait for it to complete, directly awaiting the WinRT asynchronous operation is the simplest and cleanest approach: await SomeMethodAsync() But as soon as you want more control, you’ll need to use AsTask. You’ve already seen a few such cases where this is useful: Supporting cancellation via CancellationToken CancellationToken token = ... await SomeMethodAsync().AsTask(token) Supporting progress reporting via IProgress IProgress progress = ... await SomeMethodAsync().AsTask(progress) Suppressing the default continuation marshaling behavior via ConfigureAwait await SomeMethodAsync().AsTask().ConfigureAwait(false) There are also additional important situations where AsTask can be quite useful. One situation has to do with Task’s ability to support multiple continuations. The WinRT async operation types support only a single delegate registered with Completed (Completed is a property rather than an event), and it may be set only once. This is fine for the majority of cases where you simply want to await the operation once, e.g. instead of calling a synchronous method: SomeMethod() you call and await an asynchronous counterpart: await SomeMethodAsync() logically maintaining the same control flow as if you used the synchronous counterpart. But sometimes you want to be able to hook up multiple callbacks, or you want to be able to await the same instance multiple times. In contrast to the WinRT async interfaces, the Task type does support being awaited any number of times and/or having its ContinueWith method being used any number of times to support any number of callbacks. Thus, you can use AsTask to get a task for your WinRT async operation, and then hook up your multiple callbacks to the Task rather than to the WinRT async operation directly. var t = SomeMethodAsync().AsTask() t.ContinueWith(delegate { ... }) t.ContinueWith(delegate { ... }) t.ContinueWith(delegate { ... }) Another example where AsTask can be useful is when dealing with methods that operate in terms of the Task or Task types. Combinator methods like Task.WhenAll or Task.WhenAny operate in terms of Task, not in terms of the WinRT async interfaces. So, if you wanted to be able to invoke multiple WinRT async operations and then await for all or any of them to complete, you could use AsTask to make this easy. For example, this await completes as soon as any of the three supplied operations complete, and returns the Task representing whichever it was: Task firstCompleted = await Task.WhenAny( SomeMethod1Async().AsTask(), SomeMethod2Async().AsTask(), SomeMethod3Async().AsTask()) Conclusion It’s truly exciting how much functionality WinRT via asynchronous operations provide the volume of such APIs exposed speaks to just how important responsiveness is to the platform. This in turn places a significant demand on the programming model used to work with these operations: for C# and Visual Basic code, await and AsTask rise to the occasion. Hopefully this blog post has peeled back the curtain sufficiently to give you a good sense of exactly how these capabilities work and enable you to productively develop Metro style apps. For more info, I recommend these resources: [url=http://msdn.microsoft.com/en-us/async">Visual Studio Async Programming [url=http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx">Async/Await FAQ [url=http://channel9.msdn.com/events/BUILD/BUILD2011/TOOL-810T">Async made simple in Windows 8, with C# and Visual Basic Stephen Toub Visual Studio View the full article Edited February 9, 201410 yr by AWS Off Topic Forum - Unlike the Rest
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.