在 C# 或 Visual Basic 中调用异步 API

Windows包含许多异步 API,以确保应用在完成可能需要较长时间的工作时保持响应。 本主题讨论如何在 C# 或 Microsoft Visual Basic中使用异步方法。

异步 API 使应用在继续执行之前不会等待大型操作完成。 例如,从 Internet 下载信息的应用可能需要几秒钟时间等待信息到达。 如果使用同步方法检索信息,则会阻止应用,直到该方法返回。 该应用不会响应用户交互,因为它似乎无响应,因此用户可能会感到沮丧。 通过提供异步 API,Windows有助于确保在应用执行长时间操作时保持对用户的响应。

Windows中的大多数异步 API 没有同步的对应项,因此需要确保了解如何在Windows应用中将异步 API 与 C# 或Visual Basic配合使用。 下面介绍如何调用异步 API。

使用异步 API

按照约定,异步方法的名称以“Async”结尾。 通常调用异步 API 来响应用户的操作,例如当用户单击按钮时。 在事件处理程序中调用异步方法是使用异步 API 的最简单方法之一。 此处我们使用 await 运算符作为示例。

假设你有一个应用,其中列出了来自特定位置的博客文章标题。 应用中有一个Button,用户单击该按钮即可获取标题。 标题显示在 TextBlock 中。 当用户单击按钮时,应用在等待博客网站中的信息时保持响应非常重要。 为了确保这种响应性,Windows 提供了一种异步方法 SyndicationClient.RetrieveFeedAsync,用于下载源。

以下示例通过调用异步方法 SyndicationClient.RetrieveFeedAsync 并等待结果,从博客获取博客文章列表。

// Put the keyword async on the declaration of the event handler.
private async void Button_Click_1(object sender, RoutedEventArgs e)
{

    Windows.Web.Syndication.SyndicationClient client = new SyndicationClient();

    Uri feedUri
        = new Uri("http://windowsteamblog.com/windows/b/windowsexperience/atom.aspx");

    try
    {
        SyndicationFeed feed = await client.RetrieveFeedAsync(feedUri);

        // The rest of this method executes after await RetrieveFeedAsync completes.
        rssOutput.Text = feed.Title.Text + Environment.NewLine;

        foreach (SyndicationItem item in feed.Items)
        {
            rssOutput.Text += item.Title.Text + ", " +
                             item.PublishedDate.ToString() + Environment.NewLine;
        }
    }
    catch (Exception ex)
    {
        // Log Error.
        rssOutput.Text =
            "I'm sorry, but I couldn't load the page," +
            " possibly due to network problems." +
            "Here's the error message I received: "
            + ex.ToString();
    }
}
' Put the keyword Async on the declaration of the event handler.
Private Async Sub Button_Click_1(sender As Object, e As RoutedEventArgs)
    Dim client As New Windows.Web.Syndication.SyndicationClient()
    Dim feedUri As New Uri("http://windowsteamblog.com/windows/b/windowsexperience/atom.aspx")

    Try
        Dim feed As SyndicationFeed = Await client.RetrieveFeedAsync(feedUri)

        ' The rest of this method executes after the await operation completes.
        rssOutput.Text = feed.Title.Text & vbCrLf

        For Each item In feed.Items
            rssOutput.Text += $"{item.Title.Text}, {item.PublishedDate.ToString()}{vbCrLf}"
        Next

    Catch ex As Exception
        ' Log Error.
        rssOutput.Text = "I'm sorry, but I couldn't load the page," &
                         " possibly due to network problems." &
                         "Here's the error message I received: " &
                          ex.ToString()
    End Try

End Sub

本示例有几点重要内容。 首先, SyndicationFeed feed = await client.RetrieveFeedAsync(feedUri) 该行使用 await 运算符和对异步方法 RetrieveFeedAsync 的调用。 可以将 await 运算符视为告知编译器调用异步方法,这会导致编译器执行一些额外的工作,这样就不必这样做。 接下来,事件处理程序的声明包括关键字 异步。 必须在使用 await 运算符的任何方法的方法声明中包括此关键字。

在本主题中,我们不会深入讨论编译器如何处理 await 运算符的诸多细节,而是来看看你的应用如何实现异步并保持响应。 请考虑使用同步代码时会发生什么情况。 例如,假设有一个称为 SyndicationClient.RetrieveFeed 同步的方法。 (没有这样的方法,但想象有。如果应用包含该行 SyndicationFeed feed = client.RetrieveFeed(feedUri),而不是 SyndicationFeed feed = await client.RetrieveFeedAsync(feedUri)应用的执行将停止,直到返回值 RetrieveFeed 可用为止。 当应用等待方法完成时,它无法响应任何其他事件,例如另一个 Click 事件。 也就是说,你的应用将被阻止,直到 RetrieveFeed 返回。

但是,如果调用 client.RetrieveFeedAsync,该方法将启动检索并立即返回。 将 awaitRetrieveFeedAsync 配合使用时,应用会暂时退出事件处理程序。 然后,它可以在 RetrieveFeedAsync 异步执行时处理其他事件。 这会使应用能够响应用户。 当 RetrieveFeedAsync 完成且 SyndicationFeed 可用时,应用基本上会在 SyndicationFeed feed = await client.RetrieveFeedAsync(feedUri) 之后从事件处理程序上次中断的地方重新进入,并完成该方法的其余部分。

使用 await 运算符的一个好处是,代码看起来和使用假想的 RetrieveFeed 方法时并没有太大区别。 有一些方法可以在 C# 或 Visual Basic 中不使用 await 运算符来编写异步代码,但生成的代码往往更侧重于异步执行的实现机制。 这使得异步代码难以编写、难以理解和难以维护。 通过使用 await 运算符,可以获得异步应用的优势,而无需使代码复杂。

返回异步 API 的类型和结果

如果你点击了指向 RetrieveFeedAsync 的链接,你可能已经注意到,RetrieveFeedAsync 的返回类型并不是 SyndicationFeed。 相反,返回类型为 IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress>. 从原始语法中查看,异步 API 返回一个对象,该对象包含其中的结果。 虽然人们通常会认为异步方法是可等待的,而且这种想法有时也很有用,但 await 运算符实际上作用于方法的返回值,而不是方法本身。 应用 await 运算符时,返回的结果是对方法返回的对象调用 GetResult 的结果。 在此示例中, SyndicationFeedRetrieveFeedAsync.GetResult() 的结果。

使用异步方法时,你可以检查方法签名,了解等待方法返回值后会得到什么。 所有异步 API 返回以下类型之一:

异步方法的结果类型与 TResult 类型参数相同。 没有 TResult 的类型没有结果。 可以将结果视为 无效。 在 Visual Basic 中,Sub 过程相当于一个返回类型为 void 的方法。

此处的表提供了异步方法的示例,并列出了每个方法的返回类型和结果类型。

异步方法 返回类型 结果类型
SyndicationClient.RetrieveFeedAsync IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> SyndicationFeed
FileOpenPicker.PickSingleFileAsync IAsyncOperation<StorageFile> 存储文件
XmlDocument.SaveToFileAsync IAsyncAction void
InkStrokeContainer.LoadAsync IAsyncActionWithProgress<UInt64> void
DataReader.LoadAsync DataReaderLoadOperation,一个实现 IAsyncOperation<UInt32> 的自定义结果类 UInt32

注释

FileOpenPicker 和其他选取器 API 在 WinUI 3 桌面应用中需要窗口句柄 (HWND) 初始化。 在调用 PickSingleFileAsync 之前,先调用 WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd),其中 hwnd 来自 WindowNative.GetWindowHandle(this)。 请参阅显示需要窗口句柄 (HWND) 的 WinUI 3 选取器 API

Windows应用.NET中定义的异步方法具有返回类型 TaskTask<TResult>。 返回 Task 的方法类似于返回 IAsyncAction 的异步方法。 在每个情况下,异步方法的结果为 void。 返回类型 Task<TResult 类似于 IAsyncOperation TResult><,因为运行任务时异步方法的结果与类型参数的类型相同。>TResult

错误管理

使用 await 运算符从异步方法检索结果时,可以使用 try/catch 块来处理异步方法中发生的错误,就像对同步方法所做的那样。 前面的示例在 try/catch 块中包装 RetrieveFeedAsync 方法和 await 操作,以在引发异常时处理错误。

当异步方法调用其他异步方法时,导致异常的任何异步方法都将传播到外部方法。 这意味着可以将 try/catch 块放在最外部的方法上,以捕获嵌套异步方法的错误。 同样,这与捕获同步方法异常的方式类似。 但是,不能在 catch 块中使用 await

Tip

从 C# 6.0(.NET Framework 4.6 / Visual Studio 2015 开始),可以在 catch 块中使用 await

摘要和后续步骤

我们在此处显示的调用异步方法的模式是调用事件处理程序中的异步 API 时使用的最简单方法。 在 Visual Basic 中重写返回 voidSub 的方法时调用异步方法,也可以使用此模式。

遇到异步方法时,请务必记住:

  • 按照约定,异步方法的名称以“Async”结尾。
  • 使用 await 运算符的任何方法都必须使用 异步 关键字标记其声明。
  • 当应用找到 await 运算符时,当异步方法执行时,应用将保持对用户交互的响应。
  • 等待异步方法返回的值将返回包含结果的对象。 在大多数情况下,返回值中包含的结果非常有用,而不是返回值本身。 可以通过查看异步方法的返回类型来查找结果中包含的值的类型。
  • 使用异步 API 和 异步 模式通常是提高应用的响应能力的方法。

本主题中的示例输出如下所示的文本。

Windows Experience Blog
PC Snapshot: Sony VAIO Y, 8/9/2011 10:26:56 AM -07:00
Tech Tuesday Live Twitter #Chat: Too Much Tech #win7tech, 8/8/2011 12:48:26 PM -07:00
Windows 7 themes: what's new and what's popular!, 8/4/2011 11:56:28 AM -07:00
PC Snapshot: Toshiba Satellite A665 3D, 8/2/2011 8:59:15 AM -07:00
Time for new school supplies? Find back-to-school deals on Windows 7 PCs and Office 2010, 8/1/2011 2:14:40 PM -07:00
Best PCs for blogging (or working) on the go, 8/1/2011 10:08:14 AM -07:00
Tech Tuesday – Blogging Tips and Tricks–#win7tech, 8/1/2011 9:35:54 AM -07:00
PC Snapshot: Lenovo IdeaPad U460, 7/29/2011 9:23:05 AM -07:00
GIVEAWAY: Survive BlogHer with a Sony VAIO SA and a Samsung Focus, 7/28/2011 7:27:14 AM -07:00
3 Ways to Stay Cool This Summer, 7/26/2011 4:58:23 PM -07:00
Getting RAW support in Photo Gallery & Windows 7 (…and a contest!), 7/26/2011 10:40:51 AM -07:00
Tech Tuesdays Live Twitter Chats: Photography Tips, Tricks and Essentials, 7/25/2011 12:33:06 PM -07:00
3 Tips to Go Green With Your PC, 7/22/2011 9:19:43 AM -07:00
How to: Buy a Green PC, 7/22/2011 9:13:22 AM -07:00
Windows 7 themes: the distinctive artwork of Cheng Ling, 7/20/2011 9:53:07 AM -07:00