Jump to content

Featured Replies

Posted
  • FPCH Admin

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:

public
interface
IAsyncInfo

{

AsyncStatus
Status {
get
}

Exception
ErrorCode {
get
}

uint
Id {
get
}

 

void
Cancel()

void
Close()

}

In addition to implementing IAsyncInfo, every valid WinRT asynchronous operation implements one of four additional interfaces: IAsyncAction, IAsyncActionWithProgress, 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:

// No results, no progress

public
interface
IAsyncAction
:
IAsyncInfo

{

AsyncActionCompletedHandler
Completed {
get
set
}

void
GetResults()

}

 

// No results, with progress

public
interface
IAsyncActionWithProgress
:
IAsyncInfo

{

AsyncActionWithProgressCompletedHandler
Completed {
get
set
}

AsyncActionProgressHandler
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()

}

(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:

internal
static
Task
DownloadStringAsyncInternal(
string
url)

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:

public
static
IAsyncOperation
DownloadStringAsync(
string
url)

To implement this method, we can call the DownloadStringAsyncInternal method to get the resulting Task. Then we need to convert that task to the required IAsyncOperation… but how?

public
static
IAsyncOperation
DownloadStringAsync(
string
url)

{

Task
from = DownloadStringAsyncInternal(url)

IAsyncOperation
to = ...
// TODO: how do we convert 'from'?

return
to

}

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:

// in System.Runtime.WindowsRuntime.dll

public
static
class
WindowsRuntimeSystemExtensions

{

public
static
IAsyncAction
AsAsyncAction(

this
Task
source)

public
static
IAsyncOperation
AsAsyncOperation(

this
Task
source)

...

}

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:

public static IAsyncOperation

DownloadStringAsync(string url)

{

Task
from = DownloadStringAsyncInternal(url)

IAsyncOperation
to =
from.AsAsyncOperation()

return to

}

We can also write this more succinctly, highlighting how very cast-like the operation is:

public static IAsyncOperation

DownloadStringAsync(string url)

{

return DownloadStringAsyncInternal(url)
.AsAsyncOperation()

}

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:

public static IAsyncOperation

DownloadStringAsync(string url)

{

return
Task
.Run(
()=>DownloadStringAsyncInternal(url)
)
.AsAsyncOperation()

}

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:

// in System.Runtime.WindowsRuntime.dll

public
static
class
AsyncInfo

{

// No results, no progress

public
static
IAsyncAction
Run(

Func

Task
> taskProvider)

 

// No results, with progress

public
static
IAsyncActionWithProgress
Run
(

Func

IProgress
,

Task
> taskProvider)

 

 

// With results, no progress

public
static
IAsyncOperation
Run(

Func

Task
> taskProvider)

 

// With results, with progress

public
static
IAsyncOperationWithProgress
Run(

Func

IProgress
,

Task
> taskProvider)

}

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 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:

public
static
IAsyncAction
AsAsyncAction(

this
Task
source)

{

return
AsyncInfo
.Run(_ => source)

}

 

public
static
IAsyncOperation
AsAsyncOperation(

this
Task
source)

{

return
AsyncInfo
.Run(_ => source)

}

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:

internal static
Task
DownloadStringAsyncInternal(

string
url,
CancellationToken
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 to have cancellation requested on a CancellationToken that’s passed into DownloadStringAsyncInternal? AsAsyncOperation won’t work in this case:

public
static
IAsyncOperation
DownloadStringAsync(
string
uri)

{

return
DownloadStringAsyncInternal(uri, …
/* what goes here?? */
)

.AsAsyncOperation()

}

To know when cancellation is requested of the IAsyncOperation, 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 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, 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

DownloadStringAsync(string uri)

{

return
AsyncInfo
.Run(cancellationToken =>

DownloadStringAsyncInternal(uri,
cancellationToken))

}

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:

public static IAsyncOperation

DownloadStringAsync(string uri)

{

return AsyncInfo.Run(delegate(CancellationToken cancellationToken)

{

return DownloadStringAsyncInternal(uri, cancellationToken))

})

}

 

private static async Task
DownloadStringAsyncInternal(

string uri, CancellationToken cancellationToken)

{

var response = await new HttpClient().GetAsync(

uri, cancellationToken)

response.EnsureSuccessStatusCode()

return await response.Content.ReadAsStringAsync()

}

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:

public static IAsyncOperation

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()

})

}

Progress

 

AsyncInfo.Run also provides support for progress reporting through WinRT async methods.

Instead of our DownloadStringAsync method returning an IAsyncOperation, imagine if we instead wanted it to return an IAsyncOperationWithProgress:

public
static
IAsyncOperationWithProgress
DownloadStringAsync(
string
uri)

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,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 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

DownloadStringAsync(string uri)

{

return AsyncInfo.Run(async delegate(

CancellationToken cancellationToken,
IProgress
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) }

})

}

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:

public
static
IAsyncOperation
AsAsyncOperation(

this
Task
source)

To implement this method, we need to create a type that implements IAsyncOperation and that wraps the supplied task.

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 }

 

...

}

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:

public
void
Close() {
/* NOP */
}

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:

private readonly
CancellationTokenSource
m_canceler =

new
CancellationTokenSource
()

 

public
void
Cancel() { m_canceler.Cancel() }

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.

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

}

}

}

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:

public uint
Id {
get
{
return
(
uint
)m_task.Id } }

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:

public
Exception
ErrorCode

{

get
{
return
m_task.IsFaulted ? m_task.Exception.InnerException :
null
}

}

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.

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."
)

}

}

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:

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)

}

}

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?

public
static
IAsyncOperation
Run(

Func
taskProvider)

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 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:

internal class
TaskAsAsyncOperationAdapter
: IAsyncOperation

{

private readonly
Task
m_task

 

public
TaskAsAsyncOperationAdapter
(
Task
task) { m_task = task }

 

public
TaskAsAsyncOperationAdapter
(

Func
func)

{

m_task = func(m_canceler.Token)

}

...

}

 

public
static
class
AsyncInfo

{

public
static
IAsyncOperation
Run(

Func
taskProvider)

{

return
new
TaskAsAsyncOperationAdapter
(taskProvider)

}

}

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, 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.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...