windows C++ 并行编程-PPL 中的取消操作(二)

并行模式库 (PPL) 中取消操作的角色、如何取消并行工作以及如何确定取消并行工作的时间。

运行时使用异常处理实现取消操作。 请勿在代码中捕捉或处理这些异常。 此外,还建议你在任务的函数体中编写异常安全的代码。 例如,可以使用获取资源即初始化 (RAII) 模式,以确保在任务体中引发异常时正确处理资源。

使用取消标记来取消并行工作

task、task_group 和 structured_task_group 类支持通过使用取消标记进行取消。 PPL 为此目标定义 concurrency::cancellation_token_source 和 concurrency::cancellation_token 类。 当使用取消标记来取消工作时,运行时不会启动订阅此标记的新工作。 已处于活动状态的工作可以使用 is_canceled 成员函数监视取消标记,并在可能时停止。

若要初始化取消,请调用 concurrency::cancellation_token_source::cancel 方法。 可以采用以下方法响应取消:

对于 task 对象,请使用 concurrency::cancel_current_task 函数。 cancel_current_task 取消当前任务及其任何基于值的延续。 它不会取消与任务或其延续关联的取消标记。

对于任务组和并行算法,使用 concurrency::is_current_task_group_canceling 函数取消,并在此函数返回 true 时尽快从任务正文返回。 请勿从任务组调用 cancel_current_task。

下面的示例演示用于进行任务取消的第一个基本模式。 任务正文偶尔检查循环内的取消。

// task-basic-cancellation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <concrt.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

bool do_work()
{
    // Simulate work.
    wcout << L"Performing work..." << endl;
    wait(250);
    return true;
}

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    wcout << L"Creating task..." << endl;

    // Create a task that performs work until it is canceled.
    auto t = create_task([&]
    {
        bool moreToDo = true;
        while (moreToDo)
        {
            // Check for cancellation.
            if (token.is_canceled())
            {
                // TODO: Perform any necessary cleanup here...

                // Cancel the current task.
                cancel_current_task();
            }
            else 
            {
                // Perform work.
                moreToDo = do_work();
            }
        }
    }, token);

    // Wait for one second and then cancel the task.
    wait(1000);

    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    wcout << L"Waiting for task to complete..." << endl;
    t.wait();

    wcout << L"Done." << endl;
}

/* Sample output:
    Creating task...
    Performing work...
    Performing work...
    Performing work...
    Performing work...
    Canceling task...
    Waiting for task to complete...
    Done.
*/

cancel_current_task 函数引发;因此,你不必从当前循环或函数显示返回。

或者,可以调用 concurrency::interruption_point 函数而不是 cancel_current_task。

响应取消时,请务必调用 cancel_current_task,因为任务转换为已取消状态。 如果提前返回而不是调用 cancel_current_task,则操作转换为已完成状态并且所有基于值的延续都会运行。

注意,切勿从代码中引发 task_canceled。 请改为调用 cancel_current_task。

当任务以已取消状态结束时,concurrency::task::get 方法引发 concurrency::task_canceled。 相反,concurrency::task::wait 返回 task_status::canceled 并且不会引发。下面的示例阐释了基于任务的延续的这种行为。 始终调用基于任务的延续,即使前面的任务已取消。

// task-canceled.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t1 = create_task([]() -> int
    {
        // Cancel the task.
        cancel_current_task();
    });

    // Create a continuation that retrieves the value from the previous.
    auto t2 = t1.then([](task<int> t)
    {
        try
        {
            int n = t.get();
            wcout << L"The previous task returned " << n << L'.' << endl;
        }
        catch (const task_canceled& e)
        {
            wcout << L"The previous task was canceled." << endl;
        }
    });

    // Wait for all tasks to complete.
    t2.wait();
}
/* Output:
    The previous task was canceled.
*/

由于基于值延续的继承其前面的任务的标记,因此除非它们是使用显式标记创建的,即使前面的任务仍正在执行,延续也会立即进入已取消状态。 因此,在取消后由前面的任务引发的任何异常都不会传播到延续任务。 取消始终替代前面的任务的状态。 下面的示例与前面的示例类似,但说明了基于值的延续的行为。

auto t1 = create_task([]() -> int
{
    // Cancel the task.
    cancel_current_task();
});

// Create a continuation that retrieves the value from the previous.
auto t2 = t1.then([](int n)
{
    wcout << L"The previous task returned " << n << L'.' << endl;
});

try
{
    // Wait for all tasks to complete.
    t2.get();
}
catch (const task_canceled& e)
{
    wcout << L"The task was canceled." << endl;
}
/* Output:
    The task was canceled.
*/

如果,没有将此取消标记传递给 task 构造函数或 concurrency::create_task 函数,则该任务是不可取消的。 此外,还必须将相同的取消标记传递给任何嵌套的任务,即在另一项任务的正文中创建的任务的构造函数,以同时取消所有任务。

你可能想要在取消标记被取消时运行任意代码。 例如,如果用户在用户界面上选择“取消”按钮来取消操作,则可禁用该按钮,直到用户启动其他操作。 下面的示例演示了如何使用 concurrency::cancellation_token::register_callback 方法来注册在取消标记被取消时运行的回调函数。

// task-cancellation-callback.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    // An event that is set in the cancellation callback.
    event e;

    cancellation_token_registration cookie;
    cookie = token.register_callback([&e, token, &cookie]()
    {
        wcout << L"In cancellation callback..." << endl;
        e.set();

        // Although not required, demonstrate how to unregister 
        // the callback.
        token.deregister_callback(cookie);
    });

    wcout << L"Creating task..." << endl;

    // Create a task that waits to be canceled.
    auto t = create_task([&e]
    {
        e.wait();
    }, token);

    // Cancel the task.
    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    t.wait();

    wcout << L"Done." << endl;
}
/* Sample output:
    Creating task...
    Canceling task...
    In cancellation callback...
    Done.
*/

任务并行文档介绍了基于值的延续和基于任务的延续之间的差异。 如果未向延续任务提供 cancellation_token 对象,则延续通过以下方式从前面的任务继承取消标记:

基于值的延续始终继承前面的任务的取消标记。

基于任务的延续从不继承前面的任务的取消标记。 使基于任务的延续可取消的唯一方法是显式传递一个取消标记。

这些行为不会受出错任务(即引发异常的任务)影响。 在这种情况下,基于值的延续被取消;基于任务的延续没有被取消。

注意在另一个任务中创建的任务(即嵌套任务)不会继承父任务的取消标记。 只有基于值的继承延续前面的任务的取消标记。

当你调用一个采用 cancellation_token 对象的构造函数或函数,且希望操作不可取消时,请使用 concurrency::cancellation_token::none 方法。

还可以向 task_group 或 structured_task_group 对象的构造函数提供取消标记。 一个重要方面是子任务组继承此取消标记。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值