Programming with async and await is the driving force behind delivering “fast and fluid” user experiences. Compiler diagnostics for async APIs guide developers towards best practices – it’s a wonderful example of platform and tooling working together. Since the Windows Runtime uses a slightly different implementation for async programming than .NET, Greg Paperin – a developer on the .NET base class libraries – explains how C# and Visual Basic still provide a unified development experience between the two sets of APIs. –Brandon
Asynchronous Programming in Windows Runtime APIs
Asynchronous programming is one of the central themes of the .NET Framework 4.5 and Visual Studio 2012 releases. The async and await keywords in C# 5 and Visual Basic 11 make writing and consuming asynchronous code easier than ever before. Visual Studio 2012 makes it easier to write and debug asynchronous code. And the .NET Framework 4.5 completes the Async story with new Task-based “XyzAsync” APIs, expansions of the Task Parallel Library and performance enhancements. We have blogged about some of these features recently (e.g. "Async in 4.5: Worth the Await" and "Enabling Progress and Cancellation in Async APIs").
The Windows Runtime (WinRT) has also adopted the asynchronous programming model. The WinRT APIs, introduced in Windows 8, are central to writing Windows Store apps. Most WinRT APIs that can have an execution time that is noticeable to a human user are asynchronous, and synchronous alternatives are not available. These choices were made to ensure that developers consistently picked APIs that deliver great end-user experiences.
WinRT async APIs are different than the .NET Task-based async APIs. However, you can use WinRT async APIs as seamlessly as pure .NET APIs, with the same keywords and the same usage principles. In this article we will go into detail on how you can do that.
We will begin with an overview of the most important types in the WinRT async infrastructure, and how they relate to similar .NET concepts. After that we will walk over creating a complete Windows Store App with several different asynchronous operations. We will learn how to start different kinds of asynchronous operations, how to retrieve results, and how to work with cancellations and progress monitoring.
This article assumes that you have a basic familiarity with using the async and await keywords with .NET APIs in C#. If you do not, try reading some of our previous articles on this topic first (introductory article; details on cancellation and on progress monitoring).
The WinRT Asynchronous model for .NET programmers
Before we begin, let us take a look at one example of calling an asynchronous WinRT API. The following statement fetches a list of files in the local user’s video library folder.
IReadOnlyList<StorageFile> videoFiles = await KnownFolders.VideosLibrary.GetFilesAsync();
Remarkably, GetFilesAsync() is not a .NET Task-based method, but a WinRT asynchronous API:
public interface IStorageFolder : IStorageItem {
// ...
IAsyncOperation<IReadOnlyList<StorageFile>> GetFilesAsync();
}
And yet, we use it just like a first class Task-based asynchronous method: with the await keyword. Intrigued? Let’s get started.
Key WinRT Async types
In the .NET Framework 4.5, asynchronous await-able APIs return an instance of type Task or Task<TResult>. You can think of a Task object as a receipt certifying that you requested an operation to be undertaken. You hold on to the receipt and use it to determine if and when the operation has completed, whether any errors occurred and what the result was.
You can use a Task in an await statement to ensure that subsequent statements are executed after the work represented by this "receipt" has completed and the result became available, without blocking. Operations that have a result (e.g., read some data from a stream) return an object of type Task<TResult>, and actions that cause an effect, but do not have a result return an object of type Task (e.g. send some bytes over a wire).
The proper computer science term for this "receipt"-concept is future, because the receipt (Task) represents an operation that may be completed in the future. (Note: Futures are alternatively known as promises because they represent a promise to complete an operation; some people also make subtle differences between the meaning of futures and promises, but that detail is not relevant for our current purposes.)
WinRT also has a concept of futures, however it does not use the same types as .NET to represent them. A future type in WinRT must implement one of the four interfaces IAsyncAction, IAsyncActionWithProgress, IAsyncOperation, IAsyncOperationWithProgress, and all of these four interfaces inherit from one common interface IAsyncInfo. The following diagram describes the four interfaces and lists their public APIs (see also the API reference on Windows Dev Center).
WinRT types for representing futures
Relationship between key Async types in WinRT and .NET
These four WinRT types have a close correspondence to tasks in .NET:
- IAsyncAction and IAsyncActionWithProgress<TProgress> describe an asynchronous action that does not return anything directly – they correspond to Task.
- IAsyncOperation<TResult> and IAsyncOperationWithProgress<TResult, TProgress> represent an asynchronous operation that returns a value of type TResult – they correspond to Task<TResult>.
The progress-related versions are identical to each respective interface with the additional ability to publish the progress of an on-going asynchronous operation. We will overlook this detail for now and come back to it later in the section "Cancellation and Progress in WinRT asynchronous operations". The following figure illustrates the conceptual relationship between tasks and WinRT Async Infos.
Conceptual relationship between asynchronous actions and operations in WinRT and .NET
.NET also has the IProgress interface used to monitor the progress of an asynchronous operation and the CancellationToken/CancellationTokenSource tandem used to signal that a consumer requested an operation to cancel. WinRT does not have corresponding types. Instead, cancellation and progress monitoring is achieved using member APIs of the four IAsyncXxxx types.
You may wonder about the reason behind these differences between .NET and WinRT Async types. Why not just use Task? Remember that WinRT APIs are available in languages that do not use the .NET Common Language Runtime to execute. For instance, you can write Windows Store apps in non-.NET languages such as JavaScript and native C++, and all WinRT types need to be available there too.
Task is a CLR type and is only available in languages that rely on the .NET runtime. This is why WinRT introduces its own asynchrony abstractions and .NET makes sure that these abstractions can be seamlessly imported into the Task-based universe while still accounting for their distinctive features. What does this look like? Let’s see it first-hand!
Awaiting WinRT Async APIs like Tasks
Recall the earlier example where we used await with the WinRT GetFilesAsync() method that returns an instance of IAsyncOperation<IReadOnlyList<StorageFile>> and not of Task<IReadOnlyList<StorageFile>>.
await KnownFolders.VideosLibrary.GetFilesAsync();
If we dig deeper into the workings of the "await" keyword we learn that it causes the GetAwaiter() method to be called on the awaited entity. Although the IAsyncOperation<TResult> interface returned from GetFilesAsync() does not define such a method, an equivalent extension method is defined by the .NET Framework in the System.Runtime.WindowsRuntime assembly. This extension method creates a Task that represents the specified WinRT async operation in the Task-based universe and simply uses the task’s already existing GetAwaiter() infrastructure. For that, it uses the AsTask() extension method, also defined in the System.Runtime.WindowsRuntime assembly. Here is a simplified implementation of the GetAwaiter() extension method:
public static TaskAwaiter GetAwaiter<TResult>(this IAsyncOperation<TResult> source) {
return AsTask(source).GetAwaiter();
}
The bulk of the logic that allows the .NET and WinRT asynchronous models to interoperate lies within the AsTask(..) extension method. It is a public method that you can use directly, however, in many cases the compiler will make sure it is called automatically on your behalf. Nevertheless, in some scenarios you will need to call AsTask(..) explicitly. We will discuss an example of that in section "Cancellation and Progress in WinRT asynchronous operations".
Using WinRT Async APIs in a Windows Store App based on .NET
Let’s walk through writing a small Windows Store app that makes use of several different asynchronous operations. We will use WinRT APIs to create a video file format converter – an application that lets the user select a .WMV file, converts it to an .MP4 file and stores it into the local video library.
We will focus on the async APIs and will make due with a very simple UI. Our app looks like this:
Sample video converter Windows Store App user interface after start-up
Asynchronous interactions with the user
The first thing our app needs to be able to do is to pick an input video file for conversion. Similar to traditional Windows desktop applications, there is an API for displaying a prompt for picking a file from the disk. However, in contrast to classic desktop applications we do not need to deal with modal dialog windows.
Asynchrony is all about thinking in terms of the operations that need to occur and the results of these operations, while abstracting away when the operation is actually performed (the system decides that for you). WinRT allows us to say "ask the user to pick a video file and use the result when it becomes available", and the async infrastructure will take care of displaying the input screen at the right time.
Note that the WinRT file picker takes up the full screen, but it is not modal: it does not block other UI components from working, even though you may not see them on the screen since they are covered. How many of you had to deal with annoying deadlocks caused by modal input dialogs? Asynchronous user input APIs solve this problem!
The pattern for picking an input file looks like this:
// Create a file picker component:
FileOpenPicker picker = new Windows.Storage.Pickers.FileOpenPicker();
// Configure the file picker:
picker.SuggestedStartLocation =
Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
picker.FileTypeFilter.Add(".wmv");// Request the system to ensure that the user has picked a file:
StorageFile file = await picker.PickSingleFileAsync();// The system will get to this line once the user has picked a file,
// however long this might take.
// However, the UI thread does not block and can continue executing other
// work in the meantime.
// Now the picked file can be used:
if (file == null) {
// ...
} else {
// ...
}
The PickSingleFileAsync method delivers the magic that resolves the modal UI problem. Let’s take a look at its signature:
public sealed class FileOpenPicker {
//...
public IAsyncOperation<StorageFile> PickSingleFileAsync();
}
Note that it does not return a Task. Instead, it returns an IAsyncOperation object. However, you can use it with await just like as if it was a Task. Neat, isn’t it? By the time you finished reading this article you will gain a general understanding of how this works.
Asynchronous WinRT operations can be awaited
in the same manner as managed Task-based operations.
To complete the picture, here is the complete method body for the "Pick input video"-button handler:
private async void PickInputButton_Click(Object sender, RoutedEventArgs e) {
// prepare the UI:
Log("Picking input file.");
PageState prevPageState = DisableButtons();
// Create and configure the file picker component:
FileOpenPicker picker = new Windows.Storage.Pickers.FileOpenPicker();
picker.SuggestedStartLocation =
Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
picker.FileTypeFilter.Add(".wmv");// Request the file picker to query the user for an input file asynchronously:
StorageFile file = await picker.PickSingleFileAsync();// If no file was picked (e.g. picker was aborted), do nothing:
if (file == null) {
Log("Input file selection cancelled.");
SetPageState(prevPageState);
return;
}
// If a file was picked, store the file and enable Start Conversion button:
Log("Input file selected: " + file.Path);
_inputFile = file;
SetPageState(PageState.ReadyToConvert);
InputFileName.Text = file.Path;
}
I/O-bound asynchronous operations
In the previous section we saw how to use await to make sure that your Windows Store app is not blocked while the user is performing some input. Another event that may cause an application to become unresponsive is data input and output. While some I/O operations are fast (think reading a few bytes from a modern solid state drive), others can stall your applications for a significant time (think spinning up a disk drive, accessing a network location or a web service, or just reading a very large file).
In our case we need to transcode a video file. At the very least, it involves reading the entire file and writing the transcoded version back to disk, and you know that video files can very quickly grow up to many megabytes, if not gigabytes. To ensure that your applications remain responsive, all I/O bound operations in WinRT are asynchronous, including video transcoding. Let us see how this is done.
The transcoding operation consists of two stages. We need to first determine if a specific input file can be converted to a specific output format. In our case, we want to convert a WMV file to MP4. To achieve this, WinRT provides a universal transcoder component that can answer these questions. The transcoder component will examine the required format information and the input file on the disk and it will report whether a conversion is possible by returning a PrepareTranscodeResult object. This process can take a couple of milliseconds up to a few seconds.
Second, if the conversion is indeed possible, we request from the PrepareTranscodeResult object to actually perform the conversion. At that stage, the entire input file will be read and the output file will be written. Depending on the length of the video, this process can take several minutes.
The coding pattern for this looks like this:
// Create the transcoder component and specify the output video file format:
MediaTranscoder transcoder = new MediaTranscoder();
MediaEncodingProfile profile=MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Vga);
// Analyze input file and output format to determine if conversion if possible:
// (we do this asynchronously, so the app does not freeze)
PrepareTranscodeResult transcode =
await transcoder.PrepareFileTranscodeAsync(_inputFile, outputFile, profile);
// When we continue here, the transcode result is already available.
if (!transcode.CanTranscode) {
// Transcoding of the provided input file into the specified output
// format is not supported. Report and give up.
} else {
// Transcoding is possible. Call transcode.TranscodeAsync() to perform conversion.
// We will see soon how this is done.
}
Cancellation and Progress in WinRT asynchronous operations
In the previous section, we used the await keyword with the I/O bound asynchronous operation PrepareFileTranscodeAsync(). Because it is a WinRT method, rather than returning a Task, it returns a WinRT asynchronous type; in this case, IAsyncOperation<PrepareTranscodeResult>. Notably, PrepareFileTranscodeAsync does not need to do much work to provide its result – the system only needs to examine a fraction of the input file to understand its format. Even if the user changes their mind right after clicking the conversion button, they can wait until the operation is completed.
However, the actual transcoding (achieved by the PrepareTranscodeResult.TranscodeAsync() API) can take many minutes depending on the length of the video file. A user may have every reason to want to cancel the on-going conversion process. Moreover, a user-friendly application will certainly want to communicate the progress of the operation to the user: After all, wouldn’t you want to know whether you have the time for an instant coffee, or whether you have enough time to prepare a nice latte? Or perhaps even enough time to finally clean those dishes? No, you don’t want to do that! See – the cancel feature is essential.
As discussed in our previous article on Async, Task-based asynchronous APIs that support cancellation and/or progress monitoring have overloads that take CancellationToken and/or IProgress<TProgress> parameters respectively. But if we inspect the WinRT PrepareTranscodeResult class looking for overloads of TranscodeAsync, we cannot discover any trace of CancellationToken or IProgress.
Recall that in section "Key WinRT Async types" above we mentioned that in contrast to the Task-based universe, the cancellation control and the progress observer in WinRT are located directly on the future types (IAsyncXzy). So how can we use them? We could use the object returned from TranscodeAsync() directly, instead of awaiting it. There is a Cancel() method we can call and a Progress delegate property we can set. In fact, this would work in many cases. However, this is a different thought model compared to Task-based APIs and this approach would mean that we needed to use different paradigms when consuming WinRT versus Task-based asynchronous APIs. Not ideal. Instead, we want to ensure an identical first-class experience for Task-based and WinRT futures.
And indeed, such first-class experience is available. TranscodeAsync() returns an instance of IAsyncActionWithProgress<Double>. Let’s consider what actually happens when the following line is evaluated:
await transcode.TranscodeAsync();
public static TaskAwaiter GetAwaiter<TProgress>(
this IAsyncActionWithProgress<TProgress> source) {
return AsTask(source).GetAwaiter();
}
GetAwaiter() creates a Task that represents the specified WinRT async action in the Task-based universe and then uses the task’s already existing GetAwaiter() infrastructure. The heavy lifting is done by the AsTask(..) extension method. It is a public method that you can use directly and it does have overloads that support the .NET-style cancellation and progress infrastructure.
The WindowsRuntimeSystemExtensions.AsTask() method family is a collection of factory methods that can take any of the four WinRT future types and create a Task that represents that WinRT future in the Task-based universe. In addition, AsTask factories can take CancellationToken and IProgress<TProgress> instances as parameters and appropriately connect them to the returned Task and thus to the underlying WinRT future. As a result, if a cancellation is requested using a token connected in this way, the cancellation request will be automatically forwarded to the WinRT operation by invoking its Cancel() method. Similarly, any progress updates published by the WinRT operation will be automatically forwarded to the specified progress observer.
The WindowsRuntimeSystemExtensions.AsTask() conversion methods create a Task that represents a WinRT asynchronous action or operation in the Task-based .NET world.
These conversion methods support optional parameters to specify a CancellationToken for requesting that a WinRT asynchronous procedure be cancelled, and an IProgress<T> observer to monitor the progress of the asynchronous procedure.
The following code sample demonstrates how you can use mix-and-match the WinRT async model with the .NET model using AsTask().
CancellationTokenSource _cancelControl = new CancellationTokenSource();
class TranscodeProgressHandler : IProgress<Double> {
public void Report(Double progressInfo) {
// Display a progress update to the user.
}
}
async void Transcode() {
PrepareTranscodeResult transcode = . . .
try {
await transcode.TranscodeAsync().AsTask(_cancelControl.Token,
new TranscodeProgressHandler());
} catch (OperationCanceledException) {
// There is no error.
// The transcoding simply responded to the cancellation request. Handle it.
} catch (Exception ex) {
// An error has occurred. Handle the error.
}
}
Recall that in .NET, cancellation – although not an error – is an exceptional condition, and is handled using exceptions. So if you hook up a potential cancellation request, you need to be prepared to handle an OperationCanceledException, as shown in the code sample above.
Let’s now take a look at the full implementation of the "Start conversion" and the "Cancel conversion" button handlers and the relevant helper methods in our sample app:
// The cancellation controller used to cancel an ongoing transcoding.
// May be null when transcoding is idle:
private CancellationTokenSource _transcodeCancelControl;
/// <summary>
/// "Cancel conversion"-button handler.
/// </summary>
/// <param name="sender">Ignored.</param>
/// <param name="e">Ignored</param>
private void CancelConversionButton_Click(Object sender, RoutedEventArgs e) {
// If we have an initialised cancel controller, signal a cancellation request:
CancellationTokenSource cancelControl =Volatile.Read(ref _transcodeCancelControl);
if (cancelControl != null)
cancelControl.Cancel();
}
/// <summary>
/// Initialises the output file for the currently selected output file.
/// </summary>
/// <returns>an initialised output file
/// or <code>null</code> if it could not be initialised.</returns>
private async Task<StorageFile> CreateOutputFile() {
StorageFile outputFile = null;
try {
// Construct the output file name / path:
String inputPath = _inputFile.Path;
String baseName = Path.GetFileNameWithoutExtension(inputPath);
String extension = Path.GetExtension(inputPath);
String outputFileName = baseName + ".ConversionOutput.mp4";
// Asynchronously initialise the output file in the pictures library:
outputFile = await KnownFolders.VideosLibrary.CreateFileAsync(outputFileName,
CreationCollisionOption.GenerateUniqueName);
// Done:
return outputFile;
} catch (Exception ex) {
// If we cannot obtain an output file objects, we cannot convert.
// Need another input file:
CleanupOutputFile(outputFile);
Log(ex.ToString());
SetPageState(PageState.InputFileNeeded);
return null;
}
}
/// <summary>
/// Deletes unneeded output file.
/// </summary>
/// <param name="outputFile">File to delete.</param>
private static async void CleanupOutputFile(StorageFile outputFile) {
// Nothing to delete:
if (outputFile == null)
return;
try {
// Delete specified file asynchronously.
// Deletion errors are benign to this application, we will ignore them.
await outputFile.DeleteAsync();
} catch { };
}
/// <summary>
/// Event handler for the "Start Conversion"-button.
/// 1. Init output file.
/// 2. Validate transcoding can occur.
/// 3. Perform transcoding.
/// Also hook up and handle cancellation, progress updates and errors.
/// </summary>
/// <param name="sender">Ignored</param>
/// <param name="e">Ignored</param>
private async void StartConversionButton_Click(Object sender, RoutedEventArgs e) {
// Prepare the UI:
PageState prevPageState = DisableButtons();
// Determine the output file.
// If we cannot prepare the output, we cannot start conversion:
StorageFile outputFile = await CreateOutputFile();
if (outputFile == null)
return;
// Update the UI:
OutputFileName.Text = outputFile.Path;
try {
// Defensive checks for current application state:
// (I/O files must be ready)
if (_inputFile == null)
throw new InvalidOperationException("Unexpected state: _inputFile==null");
if (outputFile == null)
throw new InvalidOperationException("Unexpected state: outputFile==null");
Log("Preparing transcode to file: " + outputFile.Path);
// Asynchronously check if input file can be transcoded to target format:
MediaEncodingProfile profile =
MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Vga);
PrepareTranscodeResult transcode =
await _transcoder.PrepareFileTranscodeAsync(_inputFile,
outputFile, profile);
// If we cannot transcode specified input to the target format, bail out:
if (!transcode.CanTranscode) {
Log("Transcode preparation failed. Reason: " +
transcode.FailureReason.ToString());
CleanupOutputFile(outputFile);
SetPageState(PageState.InputFileNeeded);
return;
}// OK, we can transcode:
// Reset progress bar and set the UI state:
ProgressBar.Value = 0;
SetPageState(PageState.Converting);// Transcode asynchronously:
await transcode.TranscodeAsync().AsTask(
_transcodeCancelControl.Token,
new Progress<Double>((p) => ProgressBar.Value = p));// Done transcoding. Update UI:
Log("Transcoding complete.");
SetPageState(PageState.InputFileNeeded);
} catch (OperationCanceledException) {
// Transcoding has been cancelled. Delete incomplete output file and reset UI:
Log("Transcoding has been cancelled");
CleanupOutputFile(outputFile);
SetPageState(prevPageState);
} catch (Exception ex) {
// Error during transcoding. Delete incomplete output file and reset the UI:
Log("Error during transcoding: " + ex.ToString());
CleanupOutputFile(outputFile);
SetPageState(prevPageState);
}
}
There are a few things to note here:
- We do not explicitly create a new type to monitor the progress. Instead we use the Progress<TProgressInfo> type and a lambda expression to specify the action to occur on progress update in-line.
- The "Start conversion"-button handler is responsible for several things: First, it asynchronously initialises the output file on the hard drive. If this fails, we bail out before the conversion is even attempted. Then, it verifies whether the specified input file can be successfully converted to the specified format. If the conversion is not possible, it also bails out. Finally, it performs the actual conversion. Note that exceptional conditions such as cancellation and errors are handled in-line using the common try-catch mechanism. During the entire process the hander updates the UI as appropriate.
- Recall that await causes the "rest of the method", i.e. the statements that follow the await statement, to be executed as a continuation of the future being awaited. I.e., when these subsequent statements are executed we know that the awaited operation has completed. If the execution proceeded with the line directly after the await – the execution has completed normally; if it jumped to the OperationCanceledException’s catch clause – the execution has been successfully cancelled; if it jumped to the default catch clause – the operation faulted. Either way – it has finished. As a result, we can do some clean-up. For instance, we will dispose the _transcodeCancelControl in the SetPageState(..) method. It is also the SetPageState(..) method that initialises the cancel control lazily – right in time when the cancel-button is activated. You can see how this is done by downloading the complete source code of the video transcoding application we discussed in this article.
Summary and Pointers
In this article we learned about the Windows Runtime (WinRT) model for asynchronous programming. The model differs from the .NET Task-based universe because it must work well with other WinRT technologies such as JavaScript and native C++. However, .NET makes is simple to await WinRT asynchronous operations in exactly the same manner as you await Task-based operations. In addition, the AsTask(..) method family allows using traditional .NET patterns for cancellation and progress monitoring of asynchronous WinRT operations (i.e. CancellationToken/CancellationTokenSource pattern and the Progress<T> pattern).
It is possible to use methods and properties available on WinRT future types directly, but this is discouraged. Doing so would require learning a different set of programming patterns and prevent the use of the await keyword. It is complicated and very easy to get wrong. It also can require explicit use of the CoreDispatcher and/or the SynchronizationContext – complex concepts that do not need to otherwise be dealt with by most application programmers who use async and await keywords for writing Windows Store apps.
We walked through writing a complete Windows Store application that uses a number of asynchronous actions and operations exposed by WinRT. You can download the complete source for this project here: .NET Asynchronous Programming for Windows Store Apps.
There is another, larger, transcoding media sample app available from the Windows Dev Center. While here we primarily focussed on consuming asynchronous APIs, that sample goes into more detail on using WinRT transcoding APIs with different options.
There is a number of articles that can help you learning more about asynchronous programming in .NET in general and how to apply it in Windows Store Apps specifically:
- "Asynchronous Programming with Async and Await (C# and Visual Basic)" docs on MSDN.
- "Asynchronous programming (Windows Store apps)" docs in Windows Dev Center.
- "Async in 4.5: Worth the Await" by Alok Shriram.
- "Async in 4.5: Enabling Progress and Cancellation in Async APIs" by Alok Shriram.
- "Keeping apps fast and fluid with asynchrony in the Windows Runtime" by Jason Olson.
- "Diving deep with WinRT and await" by Steve Toub.
- "Exposing .NET tasks as WinRT asynchronous operations" by Steve Toub.
- Parallel Programming with .NET team blog.
Enjoy!
Follow us or talk to us on Twitter — https://twitter.com/dotnet.
0 comments