本文介绍当在通用 Windows 运行时 (UWP) 应用中使用任务类生成基于 Windows 线程池的异步操作时要谨记的一些要点。
异步编程的使用是 Windows 运行时应用模型中的关键组成部分,因为它能使应用保持对用户输入的响应。 可以启动长期运行的任务,而不必阻止 UI 线程,并且可以在以后接收任务的结果。 也可以在任务在后台运行时取消任务和接收进度通知。 文档 C++ 中的异步编程提供 Visual C++ 中可用于创建 UWP 应用的异步模式的概述。 该文档介绍如何使用和创建异步 Windows 运行时操作链。 本节介绍如何使用 ppltasks.h 中的类型生成可供另一个 Windows 运行时组件使用的异步操作,以及如何控制异步工作的执行方式。
在 UWP 应用中,可以使用并行模式库 (PPL) 和异步代理库。 但是,不能使用任务计划程序或资源管理器。 本文介绍 PPL 提供的附加功能,这些功能仅适用于 UWP 应用,不适用于桌面应用。
UWP中使用并发编程的要点
- 使用 concurrency::create_async 来创建可供其他组件(可能用除 C++ 之外的语言编写)使用的异步操作;
- 使用 concurrency::progress_reporter 向调用您的异步操作的组件报告进程通知;
- 使用取消标记实现内部异步操作的取消;
- create_async 函数的行为取决于传递给它的工作函数的返回类型。 返回任务( task<T> 或 task<void>)的工作函数在调用了 create_async的上下文中同步运行。 返回 T 或 void 的工作函数在任意上下文中运行;
- 可以使用 concurrency::task::then 方法创建一个可依次运行的任务链。 在 UWP 应用中,任务延续的默认上下文取决于此任务的构造方式。 如果是通过向任务构造函数传递异步操作,或通过传递可返回异步操作的 lambda 表达式创建的任务,那么此任务所有延续的默认上下文是当前上下文。 如果任务不是从异步操作构造的,那么默认情况下,对任务的延续使用任意上下文。 可以用 concurrency::task_continuation_context 类重写默认上下文;
创建异步操作
可以使用并行模式库 (PPL) 中的任务和延续模型定义后台任务以及上一任务完成时要运行的其他任务。 这个功能由 concurrency::task 类提供。
Windows 运行时是可用于创建仅在特殊操作系统环境中运行的 UWP 应用的编程接口。 此类应用使用经授权的函数、数据类型和设备,并且从 Microsoft Store 进行分配。 Windows 运行时由应用程序二进制接口 (ABI) 表示。 ABI 是一种基础二进制协定,该协定允许 Visual C++ 等编程语言使用 Windows 运行时 API。
通过使用 Windows 运行时,可以使用各种编程语言的最佳功能并将它们合并到一个应用中。 例如,可以在 JavaScript 中创建 UI,在 C++ 组件中执行计算密集型应用程序逻辑。 在后台执行这些计算密集型操作的能力是使 UI 保持响应状态的一个关键因素。 因为 task 类特定于 C++,所以必须使用 Windows 运行时接口将异步操作传达给其他组件(可能用除 C++ 之外的语言编写)。 Windows 运行时提供四个接口,可以使用它们来表示异步操作:
- Windows::Foundation::IAsyncAction:表示异步操作。
- Windows::Foundation::IAsyncActionWithProgress<TProgress>:表示报告进度的异步操作。
- Windows::Foundation::IAsyncOperation<TResult>:表示返回结果的异步操作。
- Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>:表示返回结果并报告进度的异步操作。
动作 的概念是指异步任务不生成值(想一想返回 void的函数)。 操作 的概念是指异步任务确实会生成值。 进程 的概念是指任务可以向调用方报告进程消息。 JavaScript、.NET Framework 和 Visual C++ 均提供自己的方式来创建这些接口的实例,以便跨 ABI 边界使用。 对于 Visual C++,PPL 提供 concurrency::create_async 函数。 此函数会创建表示任务完成的 Windows 运行时异步动作或操作。 create_async 函数采用一个工作函数(通常是 lambda 表达式),以内部方式创建 task 对象,并将此任务包装到四个异步 Windows 运行时接口之一中。
仅在必须创建可从另一语言或另一 Windows 运行时组件访问的功能时才使用 create_async。 当知道操作是由 C++ 代码在同一组件中生成和使用时,可以直接使用 task 类。
create_async 的返回类型由其参数的类型决定。 例如,如果工作函数不返回值并且不报告进度,则 create_async 返回 IAsyncAction。 如果工作函数不返回值,但还会报告进度,则 create_async 返回 IAsyncActionWithProgress。 若要报告进度,请提供 concurrency::progress_reporter 对象作为工作函数的参数。 报告进度的能力使您能够报告已执行的工作量和仍然剩余的工作量(比如以百分比表示)。 还可以使您在结果可用时报告结果。
IAsyncAction、 IAsyncActionWithProgress<TProgress>、 IAsyncOperation<TResult>和 IAsyncActionOperationWithProgress<TProgress, TProgress> 接口均提供可以使您取消异步操作的 Cancel 方法。 task 类与取消标记一起使用。 当使用取消标记来取消工作时,运行时不会启动订阅此标记的新工作。 已处于活动状态的工作会监控其取消标记并在可能时停止。 文档 Cancellation in the PPL中更详细地介绍了这种机制。 可通过两种方式将任务取消与 Windows 运行时 Cancel 方法关联起来。 首先,可以定义传递给 create_async 的工作函数以采用 concurrency::cancellation_token 对象。 当调用 Cancel 方法时,将取消此取消标记,并将常规取消规则应用于支持 create_async 调用的基础 task 对象。 如果没有提供 cancellation_token 对象,则基础 task 对象会隐式定义一个。 在需要以协作方式响应工作函数中的取消时,可定义一个 cancellation_token 对象。 示例:使用 C++ 和 XAML 在 Windows 运行时应用中控制执行部分演示了一个有关如何在通用 Windows 平台 (UWP) 应用(使用自定义的 Windows 运行时 C++ 组件)中使用 C# 和 XAML 执行取消的示例。
在一个任务延续链中,当取消了取消标记时,应始终清理状态,然后调用 concurrency::cancel_current_task。 如果是提早返回而不是调用 cancel_current_task,则操作将转换为已完成状态而非已取消状态。
下表总结了在应用程序中可用于定义异步操作的组合。
可以从传递给 task 函数的工作函数返回一个值或一个 create_async 对象。 这些变体会产生不同的行为。 当返回一个值时,工作函数会包装到 task 中,以使其可在后台线程上运行。 此外,基础 task 使用隐式取消标记。 相反,如果返回 task 对象,则工作函数会同步运行。 因此,如果返回 task 对象,请确保工作函数中的任何较长操作作为任务运行,以使应用程序能够保持响应状态。 此外,基础 task 不使用隐式取消标记。 因此,在从 cancellation_token 返回 task 对象时,如果需要取消支持,则需定义工作函数以采用 create_async对象。
下面的示例演示创建可供另一 Windows 运行时组件使用的 IAsyncAction 对象的各种方式。
// Creates an IAsyncAction object and uses an implicit cancellation token.
auto op1 = create_async([]
{
// Define work here.
});
// Creates an IAsyncAction object and uses no cancellation token.
auto op2 = create_async([]
{
return create_task([]
{
// Define work here.
});
});
// Creates an IAsyncAction object and uses an explicit cancellation token.
auto op3 = create_async([](cancellation_token ct)
{
// Define work here.
});
// Creates an IAsyncAction object that runs another task and also uses an explicit cancellation token.
auto op4 = create_async([](cancellation_token ct)
{
return create_task([ct]()
{
// Define work here.
});
});