In the blog post Diving Deep with Await and WinRT, we discussed the new async and await keywords in C# and Visual Basic and how you can use them to consume Windows Runtime (WinRT) asynchronous operations.
With some assistance from the .NET Base Class Libraries (BCL), you can also use these keywords to develop asynchronous operations that are then exposed via WinRT for other components built in other languages to consume. In this post, we’ll explore how to do so. (For overall details on implementing WinRT components with C# or Visual Basic, see Creating Windows Runtime Components in C# and Visual Basic.)
Let’s start by reviewing the shape of asynchronous APIs in WinRT.
WinRT async interfaces
WinRT has several interfaces related to asynchronous operations. The first is IAsyncInfo, which every valid WinRT async operation implements. It surfaces the common capabilities for an asynchronous operation, including the operation’s current status, the operation’s error if it failed, an ID for the operation, and the operation’s ability to have cancellation requested:
In addition to implementing IAsyncInfo, every valid WinRT asynchronous operation implements one of four additional interfaces: IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation, or IAsyncOperationWithProgress. These additional interfaces allow the consumer to set a callback that will be invoked when the asynchronous work completes, and optionally allow for the consumer to get a result and/or to receive progress reports:
(IAsyncAction provides a GetResults method even though there are no results to be returned. This provides a method on a faulted async operation to throw its exception, rather than forcing all consumers to access the IAsyncInfo’s ErrorCode property. It is similar to how awaiter types in C# and Visual Basic expose a GetResult method, even if that GetResult method is typed to return void.)
When building a WinRT library, all publicly exposed asynchronous operations in that library are strongly typed to return one of these four interfaces. In contrast, new asynchronous operations exposed from .NET libraries follow the Task-based Asynchronous Pattern (TAP), returning Task or Task, the former for operations that don’t return a result, and the latter for operations that do.
Task and Task don’t implement these WinRT interfaces, nor does the Common Language Runtime (CLR) implicitly paper over the differences (as it does do for some types, such as the WinRT Windows.Foundation.Uri type and the BCL System.Uri type). Instead, we need to explicitly convert from one world to the other. In the Diving Deep with Await and WinRT post, we saw how the AsTask extension method in the BCL provides an explicit cast-like mechanism to convert from the WinRT async interfaces to .NET Tasks. The BCL also supports the other direction, with methods to convert from .NET Tasks to the WinRT async interfaces.
Converting with AsAsyncAction and AsAsyncOperation
For the purposes of this blog post, let’s assume we have a .NET asynchronous method DownloadStringAsyncInternal. We pass to it a URL to a web page, and the method asynchronously downloads and returns the contents of that page as a string:
How this method is implemented doesn’t matter. Rather, our goal is to wrap this as a WinRT asynchronous operation, meaning as a method that returns one of the four previously mentioned interfaces. As our operation has a result (a string) and as it doesn’t support progress reporting, our WinRT async operation returns IAsyncOperation<string>:
To implement this method, we can call the DownloadStringAsyncInternal method to get the resulting Task<string>. Then we need to convert that task to the required IAsyncOperation<string>… but how?
To address this gap, the System.Runtime.WindowsRuntime.dll assembly in .NET 4.5 includes extension methods for Task and Task that provide the necessary conversions:
These methods return a new IAsyncAction or IAsyncOperation instance that wraps the supplied Task or Task, respectively (because Task derives from Task, both of these methods are available for Task, though it’s relatively rare that you would use AsAsyncAction with a result-returning asynchronous method). Logically, you can think of these operations as explicit, synchronous casts, or from a design pattern perspective, as adapters. They return an instance that represents the underlying task but that exposes the required surface area for WinRT. With such extension methods, we can complete our DownloadStringAsync implementation:
We can also write this more succinctly, highlighting how very cast-like the operation is:
DownloadStringAsyncInternal is being invoked before we ever call AsAsyncOperation. This means that we need the synchronous call to DownloadStringAsyncInternal to return quickly to ensure that the DownloadStringAsync wrapper method is responsive. If for some reason you fear the synchronous work you’re doing will take too long, or if you explicitly want to offload the invocation to a thread pool for other reasons, you can do so using Task.Run, then invoking AsAsyncOperation on its returned task:
More flexibility with AsyncInfo.Run
These built-in AsAsyncAction and AsAsyncOperation extension methods are great for simple conversions from Task to IAsyncAction and from Task to IAsyncOperation. But what about more advanced conversions?
System.Runtime.WindowsRuntime.dll contains another type that provides more flexibility: AsyncInfo, in the System.Runtime.InteropServices.WindowsRuntime namespace. AsyncInfo exposes four overloads of a static Run method, one for each of the four WinRT async interfaces:
The AsAsyncAction and AsAsyncOperation methods we’ve already examined accept a Task as an argument. In contrast, these Run methods accept a function delegate that returns a Task, and this difference between Task and Func<…,Task> is enough to give us the added flexibility we need for more advanced operations.
Logically, you can think of AsAsyncAction and AsAsyncOperation as being simple helpers on top of the more advanced AsyncInfo.Run:
This isn’t exactly how they’re implemented in .NET 4.5, but functionally they behave this way, so it helps to think about them as such to contrast between the basic and advanced support. If you have a simple case, use AsAsyncAction and AsAsyncOperation, but there are several advanced cases where AsyncInfo.Run shines.
Cancellation
AsyncInfo.Run makes it possible to support cancellation with WinRT async methods.
To continue with our downloading example, let’s say we have another DownloadStringAsyncInternal overload that accepts a CancellationToken:
CancellationToken is .NET Framework type that supports cooperative cancellation in a composable manner. You can pass a single token into any number of method calls, and when that token has cancellation requested (via the CancellationTokenSource that created the token), the cancellation request is then visible to all of those consuming operations. This approach differs slightly from that used by WinRT async, which is to have each individual IAsyncInfo expose its own Cancel method. Given that, how do we arrange for a call to Cancel on the IAsyncOperation<string> to have cancellation requested on a CancellationToken that’s passed into DownloadStringAsyncInternal? AsAsyncOperation won’t work in this case:
To know when cancellation is requested of the IAsyncOperation<string>, that instance would need to somehow notify a listener that its Cancel method was called, for example by requesting cancellation of a CancellationToken that we’d pass into DownloadStringAsyncInternal. But in a classic “catch-22,” we don’t get the Task<string> on which to invoke AsAsyncOperation until we’ve already invoked DownloadStringAsyncInternal, at which point we would have already needed to supply the very CancellationToken we’d have wanted AsAsyncOperation to provide.
There are multiple ways to solve this conundrum, including the solution employed by AsyncInfo.Run. The Run method is responsible for constructing the IAsyncOperation<string>, and it creates that instance to request cancellation of a CancellationToken that it also creates when the async operation’s Cancel method is called. Then when invoking the user-supplied delegate passed to Run, it passes in this token, avoiding the previously discussed cycle:
Lambdas and Anonymous Methods
AsyncInfo.Run simplifies using lambda functions and anonymous methods to implement WinRT async methods.
For example, if we didn’t already have the DownloadStringAsyncInternal method, we might implement it and DownloadStringAsync like this:
By taking advantage of the C# and Visual Basic support for writing asynchronous anonymous methods, we can simplify our implementation by combining these two methods into one:
Progress
AsyncInfo.Run also provides support for progress reporting through WinRT async methods.
Instead of our DownloadStringAsync method returning an IAsyncOperation<string>, imagine if we instead wanted it to return an IAsyncOperationWithProgress<string,int>:
DownloadStringAsync now can provide progress updates containing integral data, such that consumers can set a delegate as the interface’s Progress property to receive notifications of progress change.
AsyncInfo.Run provides an overload that accepts a Func<CancellationToken,IProgress<TProgress>,Task>. Just as AsyncInfo.Run passes into the delegate a CancellationToken that will have cancellation requested when the Cancel method is called, it can also pass in an IProgress<TProgress> instance whose Report method triggers invocations of the consumer’s Progress delegate. For example, if we wanted to modify our previous example to report 0% progress at the beginning, 50% progress after getting the response back, and 100% progress after parsing the response into a string, that might look like this:
A look under the cover
To get a good mental model for how all of this works, let’s explore an implementation of AsAsyncOperation and AsyncInfo.Run. These are not the same implementations that exist in .NET 4.5, and they aren’t as robust. Rather, they are approximations that provide a good sense of how things work under the cover, and are not intended to be used in production.
AsAsyncOperation
The AsAsyncOperation method takes a Task and returns an IAsyncOperation:
To implement this method, we need to create a type that implements IAsyncOperation and that wraps the supplied task.
Each of the interface method implementations on this type will delegate to functionality on the wrapped task. Let’s start with the easiest members.
First, the IAsyncInfo.Close method is supposed to aggressively clean up any resources that the completed asynchronous operation used. As we have no such resources (our object just wraps a task), our implementation is empty:
With the IAsyncInfo.Cancel method a consumer of the async operation can request its cancellation. It is purely a request, and doesn’t in any way force the operation to exit. We simply use a CancellationTokenSource to store that a request occurred:
The IAsyncInfo.Status property returns an AsyncStatus to represent the current status of the operation with regards to its asynchronous lifecycle. This can be one of four values: Started, Completed, Error, or Canceled. For the most part, we can simply delegate to the underlying Task’s Status property and map from its returned TaskStatus to the needed AsyncStatus:
From TaskStatus
To AsyncStatus
RanToCompletion
Completed
Faulted
Error
Canceled
Canceled
All other values & cancellation was requested
Canceled
All other values & cancellation was not requested
Started
If the Task is not yet completed and cancellation has been requested, we need to return Canceled instead of Started. This means that although TaskStatus.Canceled is only a terminal state, AsyncStatus.Canceled can be either a terminal state or a non-terminal state, in that it’s possible for an IAsyncInfo to end in the Canceled state, or for an IAsyncInfo in the Canceled state to transition to either AsyncStatus.Completed or AsyncStatus.Error.
The IAsyncInfo.Id property returns a UInt32 identifier for the operation. As Task itself already exposes such an identifier (as an Int32), we can implement this property simply by delegating through to the underlying Task property:
WinRT defines the IAsyncInfo.ErrorCode property to return an HResult. But the CLR internally maps the WinRT HResult to a .NET Exception, surfacing it that way to us through the managed projection. Task itself exposes an Exception property, so we can just delegate through to it: if the Task ended in the Faulted state, we return its first exception (a Task could potentially fault due to multiple exceptions, such as with a Task returned from Task.WhenAll), returning null otherwise:
That’s it for implementing IAsyncInfo. Now we need to implement the two additional members supplied by IAsyncOperation: GetResults and Completed.
Consumers call GetResults after the operation completes successfully or after an exception occurs. In the former case, it returns the computed result, and in the latter case, it throws the relevant exception. If the operation ended as Canceled, or if it hasn’t ended yet, it’s illegal to invoke GetResults. As such, we can implement GetResults as follows, relying on the task’s awaiter’s GetResult method to return the result if successful or to propagate the right exception if the task ended as Faulted.
Finally, we have the Completed property. Completed represents a delegate that should be invoked when the operation completes. If the operation has already finished when Completed is set, the supplied delegate must be invoked or scheduled immediately. Additionally, a consumer can set the property only once (attempts to set multiple times result in exceptions). And after the operation has been completed, implementations must drop the reference to the delegate to avoid memory leaks. We can rely on Task’s ContinueWith method to implement much of this behavior, but because ContinueWith can be used multiple times on the same Task instance, we need to manually implement the more restrictive “set once” behavior:
With that, our implementation of AsAsyncOperation is complete, and we can use any Task-returning method as an IAsyncOperation method.
AsyncInfo.Run
Now, what about the more advanced AsyncInfo.Run?
To support this Run overload, we can use the same TaskAsAsyncOperationAdapter type we just created, with all of our existing implementation intact. In fact, we just need to augment it with the ability to work in terms of Func<CancellationToken,Task> instead of only in terms of Task. When such a delegate is provided, we can simply invoke it synchronously, passing in the m_canceler CancellationTokenSource we defined earlier and storing the returned task:
This implementation highlights that there’s no magic happening here. AsAsyncAction, AsAsyncOperation, and AsyncInfo.Run are all just helper implementations that save you from having to write all of this boilerplate yourself.
Conclusion
At this point, I hope you have a good understanding of what AsAsyncAction, AsAsyncOperation, and AsyncInfo.Run do for you: they make it easy to take a Task or a Task, and expose it as an IAsyncAction, an IAsyncOperation, an IAsyncActionWithProgress<TProgress>, or an IAsyncOperationWithProgress. Combined with the async and await keywords in C# and Visual Basic, this makes it very easy to implement new WinRT asynchronous operations in managed code.
As long as the functionality you’re exposing is available as a Task or Task, you should rely on these built-in capabilities to do the conversions for you instead of implementing the WInRT async interfaces manually. And if the functionality you’re trying to expose is not available yet as a Task or Task, try to expose it as a Task or Task first, and then rely on the built-in conversions. It can be quite difficult to get all of the semantics around a WinRT asynchronous interface implementation correct, which is why these conversions exist to do it for you. Similar support also exists if you’re implementing WinRT asynchronous operations in C++, whether through the base AsyncBase class in the Windows Runtime Library, or through the create_async function in the Parallel Pattern Library.
With some assistance from the .NET Base Class Libraries (BCL), you can also use these keywords to develop asynchronous operations that are then exposed via WinRT for other components built in other languages to consume. In this post, we’ll explore how to do so. (For overall details on implementing WinRT components with C# or Visual Basic, see Creating Windows Runtime Components in C# and Visual Basic.)
Let’s start by reviewing the shape of asynchronous APIs in WinRT.
WinRT async interfaces
WinRT has several interfaces related to asynchronous operations. The first is IAsyncInfo, which every valid WinRT async operation implements. It surfaces the common capabilities for an asynchronous operation, including the operation’s current status, the operation’s error if it failed, an ID for the operation, and the operation’s ability to have cancellation requested:
public interface IAsyncInfo
{
AsyncStatus Status { get }
Exception ErrorCode { get }
uint Id { get }
void Cancel()
void Close()
}
{
AsyncStatus Status { get }
Exception ErrorCode { get }
uint Id { get }
void Cancel()
void Close()
}
// No results, no progress
public interface IAsyncAction : IAsyncInfo
{
AsyncActionCompletedHandler Completed { get set }
void GetResults()
}
// No results, with progress
public interface IAsyncActionWithProgress<TProgress> : IAsyncInfo
{
AsyncActionWithProgressCompletedHandler<TProgress> Completed { get set }
AsyncActionProgressHandler<TProgress> Progress { get set }
void GetResults()
}
// With results, no progress
public interface IAsyncOperation : IAsyncInfo
{
AsyncOperationCompletedHandler Completed { get set }
TResult GetResults()
}
// With results, with progress
public interface IAsyncOperationWithProgress : IAsyncInfo
{
AsyncOperationWithProgressCompletedHandler Completed { get set }
AsyncOperationProgressHandler Progress { get set }
TResult GetResults()
}
public interface IAsyncAction : IAsyncInfo
{
AsyncActionCompletedHandler Completed { get set }
void GetResults()
}
// No results, with progress
public interface IAsyncActionWithProgress<TProgress> : IAsyncInfo
{
AsyncActionWithProgressCompletedHandler<TProgress> Completed { get set }
AsyncActionProgressHandler<TProgress> Progress { get set }
void GetResults()
}
// With results, no progress
public interface IAsyncOperation : IAsyncInfo
{
AsyncOperationCompletedHandler Completed { get set }
TResult GetResults()
}
// With results, with progress
public interface IAsyncOperationWithProgress : IAsyncInfo
{
AsyncOperationWithProgressCompletedHandler Completed { get set }
AsyncOperationProgressHandler Progress { get set }
TResult GetResults()
}
When building a WinRT library, all publicly exposed asynchronous operations in that library are strongly typed to return one of these four interfaces. In contrast, new asynchronous operations exposed from .NET libraries follow the Task-based Asynchronous Pattern (TAP), returning Task or Task, the former for operations that don’t return a result, and the latter for operations that do.
Task and Task don’t implement these WinRT interfaces, nor does the Common Language Runtime (CLR) implicitly paper over the differences (as it does do for some types, such as the WinRT Windows.Foundation.Uri type and the BCL System.Uri type). Instead, we need to explicitly convert from one world to the other. In the Diving Deep with Await and WinRT post, we saw how the AsTask extension method in the BCL provides an explicit cast-like mechanism to convert from the WinRT async interfaces to .NET Tasks. The BCL also supports the other direction, with methods to convert from .NET Tasks to the WinRT async interfaces.
Converting with AsAsyncAction and AsAsyncOperation
For the purposes of this blog post, let’s assume we have a .NET asynchronous method DownloadStringAsyncInternal. We pass to it a URL to a web page, and the method asynchronously downloads and returns the contents of that page as a string:
internal static Task<string> DownloadStringAsyncInternal(string url)
public static IAsyncOperation<string> DownloadStringAsync(string url)
public static IAsyncOperation<string> DownloadStringAsync(string url)
{
Task<string> from = DownloadStringAsyncInternal(url)
IAsyncOperation<string> to = ... // TODO: how do we convert 'from'?
return to
}
{
Task<string> from = DownloadStringAsyncInternal(url)
IAsyncOperation<string> to = ... // TODO: how do we convert 'from'?
return to
}
// in System.Runtime.WindowsRuntime.dll
public static class WindowsRuntimeSystemExtensions
{
public static IAsyncAction AsAsyncAction(
this Task source)
public static IAsyncOperation AsAsyncOperation(
this Task source)
...
}
public static class WindowsRuntimeSystemExtensions
{
public static IAsyncAction AsAsyncAction(
this Task source)
public static IAsyncOperation AsAsyncOperation(
this Task source)
...
}
public static IAsyncOperation<string> DownloadStringAsync(string url)
{
Task<string> from = DownloadStringAsyncInternal(url)
IAsyncOperation<string> to = from.AsAsyncOperation()
return to
}
{
Task<string> from = DownloadStringAsyncInternal(url)
IAsyncOperation<string> to = from.AsAsyncOperation()
return to
}
public static IAsyncOperation<string> DownloadStringAsync(string url)
{
return DownloadStringAsyncInternal(url).AsAsyncOperation()
}
{
return DownloadStringAsyncInternal(url).AsAsyncOperation()
}
public static IAsyncOperation<string> DownloadStringAsync(string url)
{
return Task.Run(()=>DownloadStringAsyncInternal(url)).AsAsyncOperation()
}
{
return Task.Run(()=>DownloadStringAsyncInternal(url)).AsAsyncOperation()
}
These built-in AsAsyncAction and AsAsyncOperation extension methods are great for simple conversions from Task to IAsyncAction and from Task to IAsyncOperation. But what about more advanced conversions?
System.Runtime.WindowsRuntime.dll contains another type that provides more flexibility: AsyncInfo, in the System.Runtime.InteropServices.WindowsRuntime namespace. AsyncInfo exposes four overloads of a static Run method, one for each of the four WinRT async interfaces:
// in System.Runtime.WindowsRuntime.dll
public static class AsyncInfo
{
// No results, no progress
public static IAsyncAction Run(
Func<CancellationToken,
Task> taskProvider)
// No results, with progress
public static IAsyncActionWithProgress<TProgress> Run<TProgress>(
Func<CancellationToken,
IProgress<TProgress>,
Task> taskProvider)
// With results, no progress
public static IAsyncOperation Run(
Func<CancellationToken,
Task> taskProvider)
// With results, with progress
public static IAsyncOperationWithProgress Run(
Func<CancellationToken,
IProgress<TProgress>,
Task> taskProvider)
}
public static class AsyncInfo
{
// No results, no progress
public static IAsyncAction Run(
Func<CancellationToken,
Task> taskProvider)
// No results, with progress
public static IAsyncActionWithProgress<TProgress> Run<TProgress>(
Func<CancellationToken,
IProgress<TProgress>,
Task> taskProvider)
// With results, no progress
public static IAsyncOperation Run(
Func<CancellationToken,
Task> taskProvider)
// With results, with progress
public static IAsyncOperationWithProgress Run(
Func<CancellationToken,
IProgress<TProgress>,
Task> taskProvider)
}
Logically, you can think of AsAsyncAction and AsAsyncOperation as being simple helpers on top of the more advanced AsyncInfo.Run:
public static IAsyncAction AsAsyncAction(
this Task source)
{
return AsyncInfo.Run(_ => source)
}
public static IAsyncOperation AsAsyncOperation(
this Task source)
{
return AsyncInfo.Run(_ => source)
}
this Task source)
{
return AsyncInfo.Run(_ => source)
}
public static IAsyncOperation AsAsyncOperation(
this Task source)
{
return AsyncInfo.Run(_ => source)
}
Cancellation
AsyncInfo.Run makes it possible to support cancellation with WinRT async methods.
To continue with our downloading example, let’s say we have another DownloadStringAsyncInternal overload that accepts a CancellationToken:
internal static Task<string> DownloadStringAsyncInternal(
string url, CancellationToken cancellationToken)
string url, CancellationToken cancellationToken)
public static IAsyncOperation<string> DownloadStringAsync(string uri)
{
return DownloadStringAsyncInternal(uri, … /* what goes here?? */)
.AsAsyncOperation()
}
{
return DownloadStringAsyncInternal(uri, … /* what goes here?? */)
.AsAsyncOperation()
}
There are multiple ways to solve this conundrum, including the solution employed by AsyncInfo.Run. The Run method is responsible for constructing the IAsyncOperation<string>, and it creates that instance to request cancellation of a CancellationToken that it also creates when the async operation’s Cancel method is called. Then when invoking the user-supplied delegate passed to Run, it passes in this token, avoiding the previously discussed cycle:
public static IAsyncOperation<string> DownloadStringAsync(string uri)
{
return AsyncInfo.Run(cancellationToken =>
DownloadStringAsyncInternal(uri, cancellationToken))
}
{
return AsyncInfo.Run(cancellationToken =>
DownloadStringAsyncInternal(uri, cancellationToken))
}
AsyncInfo.Run simplifies using lambda functions and anonymous methods to implement WinRT async methods.
For example, if we didn’t already have the DownloadStringAsyncInternal method, we might implement it and DownloadStringAsync like this:
public static IAsyncOperation<string> DownloadStringAsync(string uri)
{
return AsyncInfo.Run(delegate(CancellationToken cancellationToken)
{
return DownloadStringAsyncInternal(uri, cancellationToken))
})
}
private static async Task<string> DownloadStringAsyncInternal(
string uri, CancellationToken cancellationToken)
{
var response = await new HttpClient().GetAsync(
uri, cancellationToken)
response.EnsureSuccessStatusCode()
return await response.Content.ReadAsStringAsync()
}
{
return AsyncInfo.Run(delegate(CancellationToken cancellationToken)
{
return DownloadStringAsyncInternal(uri, cancellationToken))
})
}
private static async Task<string> DownloadStringAsyncInternal(
string uri, CancellationToken cancellationToken)
{
var response = await new HttpClient().GetAsync(
uri, cancellationToken)
response.EnsureSuccessStatusCode()
return await response.Content.ReadAsStringAsync()
}
public static IAsyncOperation<string> DownloadStringAsync(string uri)
{
return AsyncInfo.Run(async delegate(CancellationToken cancellationToken)
{
var response = await new HttpClient().GetAsync(
uri, cancellationToken)
response.EnsureSuccessStatusCode()
return await response.Content.ReadAsStringAsync()
})
}
{
return AsyncInfo.Run(async delegate(CancellationToken cancellationToken)
{
var response = await new HttpClient().GetAsync(
uri, cancellationToken)
response.EnsureSuccessStatusCode()
return await response.Content.ReadAsStringAsync()
})
}
AsyncInfo.Run also provides support for progress reporting through WinRT async methods.
Instead of our DownloadStringAsync method returning an IAsyncOperation<string>, imagine if we instead wanted it to return an IAsyncOperationWithProgress<string,int>:
public static IAsyncOperationWithProgress<string,int> DownloadStringAsync(string uri)
AsyncInfo.Run provides an overload that accepts a Func<CancellationToken,IProgress<TProgress>,Task>. Just as AsyncInfo.Run passes into the delegate a CancellationToken that will have cancellation requested when the Cancel method is called, it can also pass in an IProgress<TProgress> instance whose Report method triggers invocations of the consumer’s Progress delegate. For example, if we wanted to modify our previous example to report 0% progress at the beginning, 50% progress after getting the response back, and 100% progress after parsing the response into a string, that might look like this:
public static IAsyncOperationWithProgress<string,int> DownloadStringAsync(string uri)
{
return AsyncInfo.Run(async delegate(
CancellationToken cancellationToken, IProgress<int> progress)
{
progress.Report(0)
try
{
var response = await new HttpClient().GetAsync(uri, cancellationToken)
progress.Report(50)
response.EnsureSuccessStatusCode()
return await response.Content.ReadAsStringAsync()
}
finally { progress.Report(100) }
})
}
{
return AsyncInfo.Run(async delegate(
CancellationToken cancellationToken, IProgress<int> progress)
{
progress.Report(0)
try
{
var response = await new HttpClient().GetAsync(uri, cancellationToken)
progress.Report(50)
response.EnsureSuccessStatusCode()
return await response.Content.ReadAsStringAsync()
}
finally { progress.Report(100) }
})
}
To get a good mental model for how all of this works, let’s explore an implementation of AsAsyncOperation and AsyncInfo.Run. These are not the same implementations that exist in .NET 4.5, and they aren’t as robust. Rather, they are approximations that provide a good sense of how things work under the cover, and are not intended to be used in production.
AsAsyncOperation
The AsAsyncOperation method takes a Task and returns an IAsyncOperation:
public static IAsyncOperation AsAsyncOperation(
this Task source)
this Task source)
public static IAsyncOperation AsAsyncOperation(
this Task source)
{
return new TaskAsAsyncOperationAdapter(source)
}
internal class TaskAsAsyncOperationAdapter : IAsyncOperation
{
private readonly Task m_task
public TaskAsAsyncOperationAdapter(Task task) { m_task = task }
...
}
this Task source)
{
return new TaskAsAsyncOperationAdapter(source)
}
internal class TaskAsAsyncOperationAdapter : IAsyncOperation
{
private readonly Task m_task
public TaskAsAsyncOperationAdapter(Task task) { m_task = task }
...
}
First, the IAsyncInfo.Close method is supposed to aggressively clean up any resources that the completed asynchronous operation used. As we have no such resources (our object just wraps a task), our implementation is empty:
public void Close() { /* NOP */ }
private readonly CancellationTokenSource m_canceler =
new CancellationTokenSource()
public void Cancel() { m_canceler.Cancel() }
new CancellationTokenSource()
public void Cancel() { m_canceler.Cancel() }
From TaskStatus
To AsyncStatus
RanToCompletion
Completed
Faulted
Error
Canceled
Canceled
All other values & cancellation was requested
Canceled
All other values & cancellation was not requested
Started
If the Task is not yet completed and cancellation has been requested, we need to return Canceled instead of Started. This means that although TaskStatus.Canceled is only a terminal state, AsyncStatus.Canceled can be either a terminal state or a non-terminal state, in that it’s possible for an IAsyncInfo to end in the Canceled state, or for an IAsyncInfo in the Canceled state to transition to either AsyncStatus.Completed or AsyncStatus.Error.
public AsyncStatus Status
{
get
{
switch (m_task.Status)
{
case TaskStatus.RanToCompletion:
return AsyncStatus.Completed
case TaskStatus.Faulted:
return AsyncStatus.Error
case TaskStatus.Canceled:
return AsyncStatus.Canceled
default:
return m_canceler.IsCancellationRequested ?
AsyncStatus.Canceled :
AsyncStatus.Started
}
}
}
{
get
{
switch (m_task.Status)
{
case TaskStatus.RanToCompletion:
return AsyncStatus.Completed
case TaskStatus.Faulted:
return AsyncStatus.Error
case TaskStatus.Canceled:
return AsyncStatus.Canceled
default:
return m_canceler.IsCancellationRequested ?
AsyncStatus.Canceled :
AsyncStatus.Started
}
}
}
public uint Id { get { return (uint)m_task.Id } }
public Exception ErrorCode
{
get { return m_task.IsFaulted ? m_task.Exception.InnerException : null }
}
{
get { return m_task.IsFaulted ? m_task.Exception.InnerException : null }
}
Consumers call GetResults after the operation completes successfully or after an exception occurs. In the former case, it returns the computed result, and in the latter case, it throws the relevant exception. If the operation ended as Canceled, or if it hasn’t ended yet, it’s illegal to invoke GetResults. As such, we can implement GetResults as follows, relying on the task’s awaiter’s GetResult method to return the result if successful or to propagate the right exception if the task ended as Faulted.
public TResult GetResults()
{
switch (m_task.Status)
{
case TaskStatus.RanToCompletion:
case TaskStatus.Faulted:
return m_task.GetAwaiter().GetResult()
default:
throw new InvalidOperationException("Invalid GetResults call.")
}
}
{
switch (m_task.Status)
{
case TaskStatus.RanToCompletion:
case TaskStatus.Faulted:
return m_task.GetAwaiter().GetResult()
default:
throw new InvalidOperationException("Invalid GetResults call.")
}
}
private AsyncOperationCompletedHandler m_handler
private int m_handlerSet
public AsyncOperationCompletedHandler Completed
{
get { return m_handler }
set
{
if (value == null)
throw new ArgumentNullException("value")
if (Interlocked.CompareExchange(ref m_handlerSet, 1, 0) != 0)
throw new InvalidOperationException("Handler already set.")
m_handler = value
var sc = SynchronizationContext.Current
m_task.ContinueWith(delegate {
var handler = m_handler
m_handler = null
if (sc == null)
handler(this, this.Status)
else
sc.Post(delegate { handler(this, this.Status) }, null)
}, CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default)
}
}
private int m_handlerSet
public AsyncOperationCompletedHandler Completed
{
get { return m_handler }
set
{
if (value == null)
throw new ArgumentNullException("value")
if (Interlocked.CompareExchange(ref m_handlerSet, 1, 0) != 0)
throw new InvalidOperationException("Handler already set.")
m_handler = value
var sc = SynchronizationContext.Current
m_task.ContinueWith(delegate {
var handler = m_handler
m_handler = null
if (sc == null)
handler(this, this.Status)
else
sc.Post(delegate { handler(this, this.Status) }, null)
}, CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default)
}
}
AsyncInfo.Run
Now, what about the more advanced AsyncInfo.Run?
public static IAsyncOperation Run(
Func<CancellationToken,Task> taskProvider)
Func<CancellationToken,Task> taskProvider)
internal class TaskAsAsyncOperationAdapter : IAsyncOperation
{
private readonly Task m_task
public TaskAsAsyncOperationAdapter(Task task) { m_task = task }
public TaskAsAsyncOperationAdapter(
Func<CancellationToken,Task> func)
{
m_task = func(m_canceler.Token)
}
...
}
public static class AsyncInfo
{
public static IAsyncOperation Run(
Func<CancellationToken, Task> taskProvider)
{
return new TaskAsAsyncOperationAdapter(taskProvider)
}
…
}
{
private readonly Task m_task
public TaskAsAsyncOperationAdapter(Task task) { m_task = task }
public TaskAsAsyncOperationAdapter(
Func<CancellationToken,Task> func)
{
m_task = func(m_canceler.Token)
}
...
}
public static class AsyncInfo
{
public static IAsyncOperation Run(
Func<CancellationToken, Task> taskProvider)
{
return new TaskAsAsyncOperationAdapter(taskProvider)
}
…
}
Conclusion
At this point, I hope you have a good understanding of what AsAsyncAction, AsAsyncOperation, and AsyncInfo.Run do for you: they make it easy to take a Task or a Task, and expose it as an IAsyncAction, an IAsyncOperation, an IAsyncActionWithProgress<TProgress>, or an IAsyncOperationWithProgress. Combined with the async and await keywords in C# and Visual Basic, this makes it very easy to implement new WinRT asynchronous operations in managed code.
As long as the functionality you’re exposing is available as a Task or Task, you should rely on these built-in capabilities to do the conversions for you instead of implementing the WInRT async interfaces manually. And if the functionality you’re trying to expose is not available yet as a Task or Task, try to expose it as a Task or Task first, and then rely on the built-in conversions. It can be quite difficult to get all of the semantics around a WinRT asynchronous interface implementation correct, which is why these conversions exist to do it for you. Similar support also exists if you’re implementing WinRT asynchronous operations in C++, whether through the base AsyncBase class in the Windows Runtime Library, or through the create_async function in the Parallel Pattern Library.
Last edited: