【Windows8开发】异步编程进阶篇之 task group的几种方式及其间的区别

时间:2021-08-25 19:01:02
前文中曾介绍过可以通过create_task创建task并异步执行,这是对单个任务而言的,本文主要讨论任务组(task group)的管理,WinRT中提出了多种进行task group管理的方法,接下来开始一一说明。

1.  Concurrency::when_all和Concurrency::when_any
WinRT提供了when_all和when_any来控制一组任务的执行。使用when_all的代码如下:
array<task<void>, 3> tasks =
{
     create_task([] {
          // do something here
     }),
     create_task([] {
          // do something here
     }),
     create_task([] {
          // do something here
     })
};
auto joinTask = when_all(begin(tasks), end(tasks));

// Wait for the tasks to finish.
joinTask.wait();

通过when_all,可以实现等待一组任务执行完毕后再继续执行后续代码,其实也可以通过&&来实现when_all一样的功能:
auto joinTask = when_all(begin(tasks), end(tasks));
等同于
auto joinTask = tasks[0] && tasks[1] && tasks[2];

而when_any的使用方法类似于when_all,功能应该也能猜到,就是可以实现等待一组任务中的任一任务执行完毕后就继续执行后续代码,当然它也一样可以通过||来实现:
auto joinTask = when_any(begin(tasks), end(tasks));
等同于
auto joinTask = tasks[0] || tasks[1] || tasks[2];

在使用when_all和when_any时,有一点需要注意,就是其管理的所有task必须是统一形式的(参数统一,返回值统一)。

2. Concurrency::parallel_invoke
使用parallel_invoke也可以控制任务组的执行,代码如下:
auto f1 = [] { 
     create_task([] {
          // do something here
     });
};
auto f2 = [this] { 
     create_task([] {
          // do something here
     });
};
auto f3 = [] { 
     create_task([] {
          // do something here
     });
};
parallel_invoke(
     f1,
     f2,
     f3
);

parallel_invoke会等待其中所有的Lambda函数执行完毕后才会返回,但是注意了这里等待的是各Lambda函数中的处理,而如上代码在Lambda中create_task后task中的代码则是另外一回事,这些处理会在另外的background线程中执行。
有一点注意下,parallel_invoke的参数是function,而不是task。

3. task_group和structured_task_group
虽然前面两种方式也可以控制一组task的执行,但是对任务组的控制功能不够完善,比如当某个任务被终止时,API本身没有提供一种方法能通知任务组中其他的任务。而WinRT提供了两个专门针对任务组的API,task_group和structured_task_group。先看一个例子:
auto task1 = make_task([] {
     // do something here
});
auto task2 = make_task([] {
     // do something here
});
auto task3 = make_task([] {
     // do something here
});
task_group tasks;
tasks.run(task1);
tasks.run(task2);
tasks.run(task3);
tasks.wait();

使用还算简单,通过make_task可以建立任务,但该任务不会被执行,make_task的返回值是task_handle对象,task_handle其实就是封装了task中的具体处理。然后定义task_group对象,通过其run方法启动一系列task_handle中的任务,也可以通过其wait方法等待所有任务执行完毕。
那structured_task_group呢?代码类似,把上面代码中的task_group直接改为Strucutred_task_group,执行结果应该完全一样。既然如此,那structured_task_group和task_group又有何区别呢?这点是需要重点介绍的。

首先,task_group是线程安全的,也就是说你可以在不同线程中通过同一个task_group的对象向任务组中添加task或取消一个task,而structured_task_group则不是线程安全的,所以它必须在同一个线程中被创建和销毁,同时你只能在同一个线程中对其对象进行操作。
其次,基于线程安全的问题,task_group允许在调用其wait后继续往其任务组中添加任务,而structured_task_group则不被允许。
再次,task_group的run方法可以传递make_task创建的task_handle对象也可以直接传Lambda函数,而structured_task_group的run方法貌似只能传task_handle对象。
最后,在实际使用中,可以根据实际需求或者个人使用习惯来选择具体实现方式。当任务组对象需要在不同线程中使用时,则使用task_group,如果只会在同一线程中使用,则使用structured_task_group更合适,因为它执行的开销更小,效率更高
关于如何中止一个任务(task cancellation)的介绍会在后续其他文章中说明。