异步取消:.NET Framework 和 Windows 运行时之间的桥接(C# 和 Visual Basic)
您可通过组合 .NET Framework 和 Windows 运行时 的功能来最大化您的资源。 本主题中的示例演示如何使用 .NET Framework CancellationToken 实例将取消按钮添加到使用 Windows 运行时 方法从 Web 下载博客源的应用程序。
备注
若要运行示例,您必须在计算机上安装 Windows 8。此外,如要从 Visual Studio 运行示例,还必须安装 Visual Studio 2012、Visual Studio 2013、Visual Studio express 2012 for Windows 8 或Visual Studio Express 2013 for Windows。
AsTask 提供桥
取消标记需要 Task 实例,但 Windows 运行时 方法生成 IAsyncOperationWithProgress 实例。 您可使用 .NET Framework 中的 AsTask``2 扩展方法在它们之间充当桥梁。
示例中的 DownloadBlogsAsync 方法完成了大部分工作。
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);
}
}
循环的注释部分显示了详细的过渡步骤。
从在指定的 URI 下载博客源的异步操作开始,调用 SyndicationClient.RetrieveFeedAsync。 异步操作是 IAsyncOperationWithProgress 实例。
Dim feedOp As IAsyncOperationWithProgress(Of SyndicationFeed, RetrievalProgress) = client.RetrieveFeedAsync(uri)
IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> feedOp = client.RetrieveFeedAsync(uri);
因为您想使用的 .NET Framework 中的取消功能要求任务,代码应用 AsTask``1 去将 IAsyncOperationWithProgress 实例显示为 Task。 特别是,代码将应用接受 CancellationToken 参数的 AsTask 重载。
Dim feedTask As Task(Of SyndicationFeed) = feedOp.AsTask(ct)
Task<SyndicationFeed> feedTask = feedOp.AsTask(ct);
最后,await 或 Await 运算符等待任务检索 SyndicationFeed 结果。
Dim feed As SyndicationFeed = Await feedTask
SyndicationFeed feed = await feedTask;
有关 AsTask 的更多信息,请参阅 WhenAny:.NET Framework 和 Windows 运行时之间的桥接(C# 和 Visual Basic) 中的扩展起始代码。
兴趣点
您可查看整个示例,方法是滚动到本主题末尾,下载示例到您的本地计算机或生成此示例。 有关更多信息和说明,请参阅设置示例。
在您查看示例时,您会注意到突出显示重点的星号。 我们建议您阅读本节以更好地了解这些点,尤其是您之前未曾使用过 CancellationToken 的情况下。
若要实现取消按钮,您的代码必须包括以下元素。
一个 CancellationTokenSource 变量,cts,这在所有可对其进行访问的方法范围内。
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;
“取消”按钮的事件处理程序。 当用户请求取消时,事件处理程序使用 CancellationTokenSource.Cancel 方法通知 cts。
' ***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."; } }
“开始”按钮和 StartButton_Click 的事件处理程序,包含以下操作。
事件处理程序将 CancellationTokenSource,cts 实例化。
cts = New CancellationTokenSource()
// ***Instantiate the CancellationTokenSource. cts = new CancellationTokenSource();
在调用 DownloadBlogsAsync 的过程中(下载博客订阅源),代码将 cts 的 CancellationTokenSource.Token 属性当做参数进行发送。 如果请求取消,则 Token 属性会传播消息。
Await DownloadBlogsAsync(cts.Token)
await DownloadBlogsAsync(cts.Token);
DownloadBlogsAsync 调用包含于 try-catch 语句中,该语句包括在选择“取消”按钮时产生结果的 OperationCanceledException 的 catch 块。 异步方法的调用方定义要执行的操作。 此示例只显示消息。
以下代码显示了完整的 try-catch 语句。
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(); }
如之前在此主题中所讨论的,DownloadBlogsAsync 方法调用 Windows 运行时 方法和 RetrieveFeedAsync,并将 .NET Framework 扩展方法和 AsTask 运用于返回的 IAsyncOperation 实例。 AsTask 将实例表示为 Task,因此您可以将取消标记发送给异步操作。 当您选择“取消”按钮时,则该标记将传播消息。
注意,通过使用 AsTask,代码可以同时传递同一 CancellationToken 实例至 Windows 运行时 方法 (RetrieveFeedAsync) 和 .NET Framework 方法 (DownloadBlogsAsync)。
以下各行显示这部分的代码。
Dim feed As SyndicationFeed = Await client.RetrieveFeedAsync(uri).AsTask(ct)
SyndicationFeed feed = await client.RetrieveFeedAsync(uri).AsTask(ct);
如果不取消应用程序,它将生成以下输出。
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
如果在应用程序完成下载内容之前选择“取消”按钮,则结果与以下输出相似。
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.
设置示例
您可下载应用程序,自己生成应用程序,或者在不实现的情况下在本主题的末尾查看代码。 您的计算机上必须安装 Visual Studio 和 Windows 8 才能运行此应用程序。
下载完成的应用程序
解压下载的文件,然后启动 Visual Studio。
在菜单栏上,依次选择**“文件”、“打开”和“项目/解决方案”**。
导航到保存解压缩示例代码的文件夹,然后打开解决方案 (.sln) 文件。
选择 F5 键生成并运行项目。
运行代码若干次以验证是否可以在不同点撤消。
生成完成的应用程序
启动 Visual Studio。
在菜单栏上,选择**“文件”,“新建**、“项目”。
将打开**“新建项目”**对话框。
在“已安装应用程序”的“模板”类别中,选择“Visual Basic”或“Visual C#”,然后选择“Windows 应用商店”。
从项目模板列表中,选择“空白应用程序 (XAML)”。
将项目命名为 BlogFeedWithCancellation,然后选择“确定”按钮。
新项目出现在**“解决方案资源管理器”**中。
在“解决方案资源管理器”中,打开 MainPage.xaml 的快捷菜单,然后选择“打开”。
在 MainPage.xaml 的“XAML”窗口中,使用下面的代码替换代码。
<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>
包含文本框、一个“开始”按钮和一个“取消”按钮的简单窗口显示在 MainPage.xaml 的“设计”窗口中。
在“解决方案资源管理器”中,打开“MainPage.xaml.vb”或“MainPage.xaml.cs”的快捷菜单,然后选择“查看代码”。
将 MainPage.xaml.vb 或 MainPage.xaml.cs 中的代码替换为以下代码。
' 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"; } } }
选择 F5 键运行程序,然后选择“开始”按钮。
请参见
概念
WhenAny:.NET Framework 和 Windows 运行时之间的桥接(C# 和 Visual Basic)
取消一个异步任务或一组任务(C# 和 Visual Basic)
在一段时间后取消异步任务(C# 和 Visual Basic)