Async Cancellation: Bridging between the .NET Framework and the Windows Runtime (C# and Visual Basic)
You can maximize your resources by combining the capabilities of the .NET Framework and the Windows Runtime. The example in this topic shows you how to use an instance of the .NET Framework CancellationToken to add a cancellation button to an app that uses a Windows Runtime method to download blog feeds from the web.
Note
To run the example, you must have Windows 8 installed on your computer. In addition, if you want to run the example from Visual Studio, you must also have Visual Studio 2012 or Visual Studio Express 2012 for Windows 8 installed.
AsTask Provides a Bridge
The cancellation token requires Task instances, but the Windows Runtime method produces IAsyncOperationWithProgress instances. You can use the AsTask extension method in the .NET Framework to bridge between them.
The DownloadBlogsAsync method in the example does most of the work.
Async Function DownloadBlogsAsync(ct As CancellationToken) As Task
Dim client As Windows.Web.Syndication.SyndicationClient = New SyndicationClient()
Dim uriList = CreateUriList()
' Force the SyndicationClient to download the information.
client.BypassCacheOnRetrieve = True
' The following code avoids the use of implicit typing (var) so that you
' can identify the types clearly.
For Each uri In uriList
' ***These three lines are combined in the single statement that follows them.
'Dim feedOp As IAsyncOperationWithProgress(Of SyndicationFeed, RetrievalProgress) =
' client.RetrieveFeedAsync(uri)
'Dim feedTask As Task(Of SyndicationFeed) = feedOp.AsTask(ct)
'Dim feed As SyndicationFeed = Await feedTask
' ***You can combine the previous three steps in one expression.
Dim feed As SyndicationFeed = Await client.RetrieveFeedAsync(uri).AsTask(ct)
DisplayResults(feed, ct)
Next
End Function
async Task DownloadBlogsAsync(CancellationToken ct)
{
Windows.Web.Syndication.SyndicationClient client = new SyndicationClient();
var uriList = CreateUriList();
// Force the SyndicationClient to download the information.
client.BypassCacheOnRetrieve = true;
// The following code avoids the use of implicit typing (var) so that you
// can identify the types clearly.
foreach (var uri in uriList)
{
// ***These three lines are combined in the single statement that follows them.
//IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> feedOp =
// client.RetrieveFeedAsync(uri);
//Task<SyndicationFeed> feedTask = feedOp.AsTask(ct);
//SyndicationFeed feed = await feedTask;
// ***You can combine the previous three steps in one expression.
SyndicationFeed feed = await client.RetrieveFeedAsync(uri).AsTask(ct);
DisplayResults(feed);
}
}
The commented-out section in the loop shows the transition steps in detail.
The call to SyndicationClient.RetrieveFeedAsync starts an asynchronous operation that downloads a blog feed from a specified URI. The asynchronous operation is an IAsyncOperationWithProgress instance.
Dim feedOp As IAsyncOperationWithProgress(Of SyndicationFeed, RetrievalProgress) = client.RetrieveFeedAsync(uri)
IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> feedOp = client.RetrieveFeedAsync(uri);
Because the cancellation capabilities in the .NET Framework that you want to use require tasks, the code applies AsTask to represent the IAsyncOperationWithProgress instance as a Task<TResult>. In particular, the code applies an AsTask overload that accepts a CancellationToken argument.
Dim feedTask As Task(Of SyndicationFeed) = feedOp.AsTask(ct)
Task<SyndicationFeed> feedTask = feedOp.AsTask(ct);
Finally, the await or Await operator awaits the task to retrieve the SyndicationFeed result.
Dim feed As SyndicationFeed = Await feedTask
SyndicationFeed feed = await feedTask;
For more information about AsTask, see Extending the Starter Code in WhenAny: Bridging between the .NET Framework and the Windows Runtime (C# and Visual Basic).
Points of Interest
You can review the entire example by scrolling to the end of this topic, by downloading the example to your local computer, or by building the example. For more information and instructions, see Setting Up the Example.
As you review the example, you'll notice asterisks that highlight important points. We recommend that you read this section to better understand these points, especially if you haven't used CancellationToken before.
To implement a cancellation button, your code must include the following elements.
A CancellationTokenSource variable, cts, that’s in scope for all methods that access it.
Public NotInheritable Class MainPage Inherits Page ' ***Declare a System.Threading.CancellationTokenSource. Dim cts As CancellationTokenSource
public sealed partial class MainPage : Page { // ***Declare a System.Threading.CancellationTokenSource. CancellationTokenSource cts;
An event handler for the Cancel button. The event handler uses the CancellationTokenSource.Cancel method to notify cts when the user requests cancellation.
' ***Add an event handler for the Cancel button. Private Sub cancelButton_Click(sender As Object, e As RoutedEventArgs) If cts IsNot Nothing Then cts.Cancel() ResultsTextBox.Text &= vbCrLf & "Downloads canceled by the Cancel button." End If End Sub
// ***Add an event handler for the Cancel button. private void CancelButton_Click(object sender, RoutedEventArgs e) { if (cts != null) { cts.Cancel(); ResultsTextBox.Text += "\r\nDownloads canceled by the Cancel button."; } }
An event handler for the Start button, StartButton_Click, that includes the following actions.
The event handler instantiates the CancellationTokenSource, cts.
cts = New CancellationTokenSource()
// ***Instantiate the CancellationTokenSource. cts = new CancellationTokenSource();
In the call to DownloadBlogsAsync, which downloads the blog feeds, the code sends the CancellationTokenSource.Token property of cts as an argument. The Token property propagates the message if cancellation is requested.
Await DownloadBlogsAsync(cts.Token)
await DownloadBlogsAsync(cts.Token);
The call to DownloadBlogsAsync is housed in a try-catch statement that includes a catch block for the OperationCanceledException that results when you choose the Cancel button. The caller of the async method defines what action to take. This example just displays a message.
The following code shows the full try-catch statement.
Try ' ***Send a token to carry the message if cancellation is requested. Await DownloadBlogsAsync(cts.Token) ' ***Check for cancellations. Catch op As OperationCanceledException ' In practice, this catch block often is empty. It is used to absorb ' the exception, ResultsTextBox.Text &= vbCrLf & "Cancellation exception bubbles up to the caller." ' Check for other exceptions. Catch ex As Exception ResultsTextBox.Text = "Page could not be loaded." & vbCrLf & "Exception: " & ex.ToString() End Try
try { // ***Send a token to carry the message if cancellation is requested. await DownloadBlogsAsync(cts.Token); } // ***Check for cancellations. catch (OperationCanceledException) { // In practice, this catch block often is empty. It is used to absorb // the exception, ResultsTextBox.Text += "\r\nCancellation exception bubbles up to the caller."; } // Check for other exceptions. catch (Exception ex) { ResultsTextBox.Text = "Page could not be loaded.\r\n" + "Exception: " + ex.ToString(); }
As described previously in this topic, the DownloadBlogsAsync method calls the Windows Runtime method, RetrieveFeedAsync, and applies a .NET Framework extension method, AsTask, to the returned IAsyncOperation instance. AsTask represents the instance as a Task, so that you can send the cancellation token to the asynchronous operation. The token carries the message when you choose the Cancel button.
Note that by using AsTask, the code can pass the same CancellationToken instance to both a Windows Runtime method (RetrieveFeedAsync) and a .NET Framework method (DownloadBlogsAsync).
The following line shows this part of the code.
Dim feed As SyndicationFeed = Await client.RetrieveFeedAsync(uri).AsTask(ct)
SyndicationFeed feed = await client.RetrieveFeedAsync(uri).AsTask(ct);
If you don’t cancel the app, it produces the following output.
Developing for Windows New blog for Windows 8 app developers, 5/1/2012 2:33:02 PM -07:00 Trigger-Start Services Recipe, 3/24/2011 2:23:01 PM -07:00 Windows Restart and Recovery Recipe, 3/21/2011 2:13:24 PM -07:00 Extreme Windows Blog Samsung Series 9 27” PLS Display: Amazing Picture, 8/20/2012 2:41:48 PM -07:00 NVIDIA GeForce GTX 660 Ti Graphics Card: Affordable Graphics Powerhouse, 8/16/2012 10:56:19 AM -07:00 HP Z820 Workstation: Rising To the Challenge, 8/14/2012 1:57:01 PM -07:00 Blogging Windows Windows Upgrade Offer Registration Now Available, 8/20/2012 1:01:00 PM -07:00 Windows 8 has reached the RTM milestone, 8/1/2012 9:00:00 AM -07:00 Windows 8 will be available on…, 7/18/2012 1:09:00 PM -07:00 Windows for your Business What Windows 8 RTM Means for Businesses, 8/1/2012 9:01:00 AM -07:00 Higher-Ed Learning with Windows 8, 7/26/2012 12:03:00 AM -07:00 Second Public Beta of App-V 5.0 Now Available with Office Integration, 7/24/2012 10:07:26 AM -07:00 Windows Experience Blog Tech Tuesday Live Twitter Chat with Microsoft Hardware, 8/20/2012 2:20:57 AM -07:00 New Colors and New Artist Series Mice from Microsoft Hardware, 8/15/2012 12:06:35 AM -07:00 Tech Tuesday Live Twitter Chat with HP on Keeping Kids Safe as They Head Back to School #winchat, 8/13/2012 12:24:18 PM -07:00 Windows Security Blog Dealing with Fake Tech Support & Phone Scams, 6/16/2011 1:53:00 PM -07:00 Combating social engineering tactics, like cookiejacking, to stay safer online, 5/28/2011 12:02:26 PM -07:00 Windows 7 is now Common Criteria Certified!, 4/27/2011 9:35:01 AM -07:00 Windows Home Server Blog Connecting Windows 8 Consumer Preview with Windows Home Server, 3/25/2012 9:06:00 AM -07:00 Viridian PC Systems announces two new server models are available to order, 10/3/2011 12:36:00 PM -07:00 PC Specialist to release Windows Home Server 2011, 9/27/2011 10:27:37 AM -07:00 Springboard Series Blog Windows 8 Is Ready For Your Enterprise, 8/16/2012 9:59:00 AM -07:00 What to Expect in User Experience Virtualization Beta 2, 6/25/2012 11:03:27 PM -07:00 Introducing Microsoft BitLocker Administration 2.0 Beta, 6/12/2012 8:08:23 AM -07:00
If you choose the Cancel button before the app finishes downloading the content, the result resembles the following output.
Developing for Windows New blog for Windows 8 app developers, 5/1/2012 2:33:02 PM -07:00 Trigger-Start Services Recipe, 3/24/2011 2:23:01 PM -07:00 Windows Restart and Recovery Recipe, 3/21/2011 2:13:24 PM -07:00 Extreme Windows Blog Samsung Series 9 27” PLS Display: Amazing Picture, 8/20/2012 2:41:48 PM -07:00 NVIDIA GeForce GTX 660 Ti Graphics Card: Affordable Graphics Powerhouse, 8/16/2012 10:56:19 AM -07:00 HP Z820 Workstation: Rising To the Challenge, 8/14/2012 1:57:01 PM -07:00 Blogging Windows Windows Upgrade Offer Registration Now Available, 8/20/2012 1:01:00 PM -07:00 Windows 8 has reached the RTM milestone, 8/1/2012 9:00:00 AM -07:00 Windows 8 will be available on…, 7/18/2012 1:09:00 PM -07:00 Windows for your Business What Windows 8 RTM Means for Businesses, 8/1/2012 9:01:00 AM -07:00 Higher-Ed Learning with Windows 8, 7/26/2012 12:03:00 AM -07:00 Second Public Beta of App-V 5.0 Now Available with Office Integration, 7/24/2012 10:07:26 AM -07:00 Downloads canceled by the Cancel button. Cancellation exception bubbles up to the caller.
Setting Up the Example
You can download the app, build it yourself, or review the code at the end of this topic without implementing it. Visual Studio 2012 and Windows 8 must be installed on your computer to run this app.
To download the finished app
Download the compressed file from Async Sample: Bridging between .NET and Windows Runtime (AsTask & Cancellation).
Decompress the file that you downloaded, and then start Visual Studio.
On the menu bar, choose File, Open, Project/Solution.
Navigate to the folder that holds the decompressed sample code, and then open the solution (.sln) file.
Choose the F5 key to build and run the project.
Run the code several times to verify that you can cancel at different points.
To build the finished app
Start Visual Studio.
On the menu bar, choose File, New, Project.
The New Project dialog box opens.
In the Installed, Templates category, choose Visual Basic or Visual C#, and then choose Windows Store.
From the list of project types, choose Blank App (XAML).
Name the project BlogFeedWithCancellation, and then choose the OK button.
The new project appears in Solution Explorer.
In Solution Explorer, open the shortcut menu for MainPage.xaml, and then choose Open.
In the XAML window of MainPage.xaml, replace the code with the following code.
<Page x:Class="BlogFeedWithCancellation.MainPage" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:BlogFeedWithCancellation" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <Button x:Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="325,77,0,0" VerticalAlignment="Top" Click="StartButton_Click" Height="145" Background="#FFA89B9B" FontSize="36" Width="355" /> <Button x:Name="CancelButton" Content="Cancel" HorizontalAlignment="Left" Margin="684,77,0,0" VerticalAlignment="Top" Height="145" Background="#FFA89B9B" Click="CancelButton_Click" FontSize="36" Width="355" /> <TextBox x:Name="ResultsTextBox" HorizontalAlignment="Left" Margin="325,222,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="546" FontSize="10" ScrollViewer.VerticalScrollBarVisibility="Visible" Width="711" /> </Grid> </Page>
A simple window that contains a text box, a start button, and a cancel button appears in the Design window of MainPage.xaml.
In Solution Explorer, open the shortcut menu for MainPage.xaml.vb or MainPage.xaml.cs, and then choose View Code.
Replace the code in MainPage.xaml.vb or MainPage.xaml.cs with the following code.
' Add an Imports statement for SyndicationClient. Imports Windows.Web.Syndication ' Add an Imports statement for Tasks. Imports System.Threading.Tasks ' Add an Imports statement for CancellationToken. Imports System.Threading Public NotInheritable Class MainPage Inherits Page ' ***Declare a System.Threading.CancellationTokenSource. Dim cts As CancellationTokenSource Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs) ResultsTextBox.Text = "" ' Prevent unexpected reentrance. StartButton.IsEnabled = False ' ***Instantiate the CancellationTokenSource. cts = New CancellationTokenSource() Try ' ***Send a token to carry the message if cancellation is requested. Await DownloadBlogsAsync(cts.Token) ' ***Check for cancellations. Catch op As OperationCanceledException ' In practice, this catch block often is empty. It is used to absorb ' the exception, ResultsTextBox.Text &= vbCrLf & "Cancellation exception bubbles up to the caller." ' Check for other exceptions. Catch ex As Exception ResultsTextBox.Text = "Page could not be loaded." & vbCrLf & "Exception: " & ex.ToString() End Try ' ***Set the CancellationTokenSource to null when the work is complete. cts = Nothing ' In case you want to try again. StartButton.IsEnabled = True End Sub ' Provide a parameter for the CancellationToken. Async Function DownloadBlogsAsync(ct As CancellationToken) As Task Dim client As Windows.Web.Syndication.SyndicationClient = New SyndicationClient() Dim uriList = CreateUriList() ' Force the SyndicationClient to download the information. client.BypassCacheOnRetrieve = True ' The following code avoids the use of implicit typing (var) so that you ' can identify the types clearly. For Each uri In uriList ' ***These three lines are combined in the single statement that follows them. 'Dim feedOp As IAsyncOperationWithProgress(Of SyndicationFeed, RetrievalProgress) = ' client.RetrieveFeedAsync(uri) 'Dim feedTask As Task(Of SyndicationFeed) = feedOp.AsTask(ct) 'Dim feed As SyndicationFeed = Await feedTask ' ***You can combine the previous three steps in one expression. Dim feed As SyndicationFeed = Await client.RetrieveFeedAsync(uri).AsTask(ct) DisplayResults(feed, ct) Next End Function ' ***Add an event handler for the Cancel button. Private Sub cancelButton_Click(sender As Object, e As RoutedEventArgs) If cts IsNot Nothing Then cts.Cancel() ResultsTextBox.Text &= vbCrLf & "Downloads canceled by the Cancel button." End If End Sub Function CreateUriList() As List(Of Uri) ' Create a list of URIs. Dim uriList = New List(Of Uri) From { New Uri("https://windowsteamblog.com/windows/b/developers/atom.aspx"), New Uri("https://windowsteamblog.com/windows/b/extremewindows/atom.aspx"), New Uri("https://windowsteamblog.com/windows/b/bloggingwindows/atom.aspx"), New Uri("https://windowsteamblog.com/windows/b/business/atom.aspx"), New Uri("https://windowsteamblog.com/windows/b/windowsexperience/atom.aspx"), New Uri("https://windowsteamblog.com/windows/b/windowssecurity/atom.aspx"), New Uri("https://windowsteamblog.com/windows/b/windowshomeserver/atom.aspx"), New Uri("https://windowsteamblog.com/windows/b/springboard/atom.aspx") } Return uriList End Function ' You can pass the CancellationToken to this method if you think you might use a ' cancellable API here in the future. Sub DisplayResults(sf As SyndicationFeed, ct As CancellationToken) ' Title of the blog. ResultsTextBox.Text &= sf.Title.Text & vbCrLf ' Titles and dates for the first three blog posts. For i As Integer = 0 To If(sf.Items.Count >= 3, 2, sf.Items.Count) ResultsTextBox.Text &= vbTab & sf.Items.ElementAt(i).Title.Text & ", " & sf.Items.ElementAt(i).PublishedDate.ToString() & vbCrLf Next ResultsTextBox.Text &= vbCrLf End Sub End Class
using System; using System.Collections.Generic; using System.IO; using System.Linq; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; // Add a using directive for SyndicationClient. using Windows.Web.Syndication; // Add a using directive for Tasks. using System.Threading.Tasks; // Add a using directive for CancellationToken. using System.Threading; namespace BlogFeedWithCancellation { public sealed partial class MainPage : Page { // ***Declare a System.Threading.CancellationTokenSource. CancellationTokenSource cts; public MainPage() { this.InitializeComponent(); } private async void StartButton_Click(object sender, RoutedEventArgs e) { ResultsTextBox.Text = ""; // Prevent unexpected reentrance. StartButton.IsEnabled = false; // ***Instantiate the CancellationTokenSource. cts = new CancellationTokenSource(); try { // ***Send a token to carry the message if cancellation is requested. await DownloadBlogsAsync(cts.Token); } // ***Check for cancellations. catch (OperationCanceledException) { // In practice, this catch block often is empty. It is used to absorb // the exception, ResultsTextBox.Text += "\r\nCancellation exception bubbles up to the caller."; } // Check for other exceptions. catch (Exception ex) { ResultsTextBox.Text = "Page could not be loaded.\r\n" + "Exception: " + ex.ToString(); } // ***Set the CancellationTokenSource to null when the work is complete. cts = null; // In case you want to try again. StartButton.IsEnabled = true; } // ***Provide a parameter for the CancellationToken. async Task DownloadBlogsAsync(CancellationToken ct) { Windows.Web.Syndication.SyndicationClient client = new SyndicationClient(); var uriList = CreateUriList(); // Force the SyndicationClient to download the information. client.BypassCacheOnRetrieve = true; // The following code avoids the use of implicit typing (var) so that you // can identify the types clearly. foreach (var uri in uriList) { // ***These three lines are combined in the single statement that follows them. //IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> feedOp = // client.RetrieveFeedAsync(uri); //Task<SyndicationFeed> feedTask = feedOp.AsTask(ct); //SyndicationFeed feed = await feedTask; // ***You can combine the previous three steps in one expression. SyndicationFeed feed = await client.RetrieveFeedAsync(uri).AsTask(ct); DisplayResults(feed); } } // ***Add an event handler for the Cancel button. private void CancelButton_Click(object sender, RoutedEventArgs e) { if (cts != null) { cts.Cancel(); ResultsTextBox.Text += "\r\nDownloads canceled by the Cancel button."; } } List<Uri> CreateUriList() { // Create a list of URIs. List<Uri> uriList = new List<Uri> { new Uri("https://windowsteamblog.com/windows/b/developers/atom.aspx"), new Uri("https://windowsteamblog.com/windows/b/extremewindows/atom.aspx"), new Uri("https://windowsteamblog.com/windows/b/bloggingwindows/atom.aspx"), new Uri("https://windowsteamblog.com/windows/b/business/atom.aspx"), new Uri("https://windowsteamblog.com/windows/b/windowsexperience/atom.aspx"), new Uri("https://windowsteamblog.com/windows/b/windowssecurity/atom.aspx"), new Uri("https://windowsteamblog.com/windows/b/windowshomeserver/atom.aspx"), new Uri("https://windowsteamblog.com/windows/b/springboard/atom.aspx") }; return uriList; } // You can pass the CancellationToken to this method if you think you might use a // cancellable API here in the future. void DisplayResults(SyndicationFeed sf) { // Title of the blog. ResultsTextBox.Text += sf.Title.Text + "\r\n"; // Titles and dates for the first three blog posts. for (int i = 0; i < (sf.Items.Count < 3 ? sf.Items.Count : 3); i++) // Is Math.Min better? { ResultsTextBox.Text += "\t" + sf.Items.ElementAt(i).Title.Text + ", " + sf.Items.ElementAt(i).PublishedDate.ToString() + "\r\n"; } ResultsTextBox.Text += "\r\n"; } } }
Choose the F5 key to run the program, and then choose the Start button.
See Also
Concepts
WhenAny: Bridging between the .NET Framework and the Windows Runtime (C# and Visual Basic)
Async: Cancel a Task or a List of Tasks (C# and Visual Basic)
Cancel Tasks after a Period of Time (C# and Visual Basic)
Cancel Remaining Tasks after One Is Complete (C# and Visual Basic)