windows C++ 并行编程-并发的异常处理(一)

并发运行时使用 C++ 异常处理来传达多种错误。 这些错误包括:无效使用运行时、无法获取资源等运行时错误,以及你提供给任务和任务组的工作函数中发生的错误。 当任务或任务组引发异常时,运行时会保存该异常并将其编组到等待任务或任务组完成的上下文。 对于轻量级任务和代理等组件,运行时不会为你管理异常。 在这些情况下,你必须实现自己的异常处理机制。 本系列中描述运行时如何处理任务、任务组、轻量级任务和异步代理引发的异常,以及如何在应用程序中响应异常,其中的要点如下: 

  • 当任务或任务组引发异常时,运行时会保存该异常并将其编组到等待任务或任务组完成的上下文;
  • 如果可能,请在对 concurrency::task::get 和 concurrency::task::wait 的每个调用中加上一个 try/catch 块,以处理可以从中恢复的错误。 如果任务发生异常并且该异常未被任务、其延续任务之一或主要应用捕获,则运行时将终止应用;
  • 基于任务的延续始终会运行;无论前面的任务是否成功完成、引发异常还是被取消。 如果前面的任务引发异常或取消,则基于值的延续不会运行;
  • 由于基于任务的延续始终运行,因此请考虑是否在延续链的末尾添加基于任务的延续。 这有助于保证你的代码遵守所有异常;
  • 调用 concurrency::task::get 并且该任务被取消时,运行时会引发 concurrency::task_canceled;
  • 运行时不管理轻量级任务和代理的异常;
任务和延续

本节介绍运行时如何处理由 concurrency::task 对象及其延续引发的异常。 有关任务和延续模型的更多信息,请参阅任务并行。

当你在传递给 task 对象的工作函数的主体中引发异常时,运行时会存储该异常并将其编组到调用 concurrency::task::get 或 concurrency::task::wait 的上下文中。 任务并行化描述了基于任务与基于值的延续,但总而言之,基于值的延续采用 T 类型的参数,而基于任务的延续采用 task<T> 类型的参数。 如果引发的任务具有一个或多个基于值的延续,则不会安排这些延续运行。 下面的示例阐释了这种行为:

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

using namespace concurrency;
using namespace std;

int wmain()
{
    wcout << L"Running a task..." << endl;
    // Create a task that throws.
    auto t = create_task([]
    {
        throw exception();
    });
    
    // Create a continuation that prints its input value.
    auto continuation = t.then([]
    {
        // We do not expect this task to run because
        // the antecedent task threw.
        wcout << L"In continuation task..." << endl;
    });

    // Wait for the continuation to finish and handle any 
    // error that occurs.
    try
    {
        wcout << L"Waiting for tasks to finish..." << endl;
        continuation.wait();

        // Alternatively, call get() to produce the same result.
        //continuation.get();
    }
    catch (const exception& e)
    {
        wcout << L"Caught exception." << endl;
    }
}
/* Output:
    Running a task...
    Waiting for tasks to finish...
    Caught exception.
*/

基于任务的延续使你能够处理由先前任务引发的任何异常。 基于任务的延续始终会运行;无论任务是否成功完成、引发异常还是被取消。 当任务引发异常时,其基于任务的延续将计划运行。 下面的示例显示了一个始终引发的任务。 该任务有两个延续;一个任务是基于值的,另一个任务是基于任务的。 基于任务的异常始终会运行,因此可以捕获先前任务引发的异常。 当示例等待两个延续完成时,再次引发异常,因为在调用 task::get 或 task::wait 时始终会引发任务异常。

// eh-continuations.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{    
    wcout << L"Running a task..." << endl;
    // Create a task that throws.
    auto t = create_task([]() -> int
    {
        throw exception();
        return 42;
    });

    //
    // Attach two continuations to the task. The first continuation is  
    // value-based; the second is task-based.

    // Value-based continuation.
    auto c1 = t.then([](int n)
    {
        // We don't expect to get here because the antecedent 
        // task always throws.
        wcout << L"Received " << n << L'.' << endl;
    });

    // Task-based continuation.
    auto c2 = t.then([](task<int> previousTask)
    {
        // We do expect to get here because task-based continuations
        // are scheduled even when the antecedent task throws.
        try
        {
            wcout << L"Received " << previousTask.get() << L'.' << endl;
        }
        catch (const exception& e)
        {
            wcout << L"Caught exception from previous task." << endl;
        }
    });

    // Wait for the continuations to finish.
    try
    {
        wcout << L"Waiting for tasks to finish..." << endl;
        (c1 && c2).wait();
    }
    catch (const exception& e)
    {
        wcout << L"Caught exception while waiting for all tasks to finish." << endl;
    }
}
/* Output:
    Running a task...
    Waiting for tasks to finish...
    Caught exception from previous task.
    Caught exception while waiting for all tasks to finish.
*/

我们建议你使用基于任务的延续来捕获你能够处理的异常。 由于基于任务的延续始终运行,因此请考虑是否在延续链的末尾添加基于任务的延续。 这有助于保证你的代码遵守所有异常。 以下示例显示了一个基本的基于值的延续链。 链中的第三个任务引发异常,因此它后面的任何基于值的延续都不会运行。 然而,最终的延续是基于任务的,因此始终会运行。 最后的延续处理第三个任务引发的异常。

我们建议你尽可能捕获最具体的异常。 如果你没有要捕获的特定异常,则可以忽略这个最终的基于任务的延续。 任何异常都将保持未处理状态,并且可以终止应用程序。

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

using namespace concurrency;
using namespace std;

int wmain()
{
    int n = 1;
    create_task([n]
    {
        wcout << L"In first task. n = ";
        wcout << n << endl;
        
        return n * 2;

    }).then([](int n)
    {
        wcout << L"In second task. n = ";
        wcout << n << endl;

        return n * 2;

    }).then([](int n)
    {
        wcout << L"In third task. n = ";
        wcout << n << endl;

        // This task throws.
        throw exception();
        // Not reached.
        return n * 2;

    }).then([](int n)
    {
        // This continuation is not run because the previous task throws.
        wcout << L"In fourth task. n = ";
        wcout << n << endl;

        return n * 2;

    }).then([](task<int> previousTask)
    {
        // This continuation is run because it is task-based.
        try
        {
            // The call to task::get rethrows the exception.
            wcout << L"In final task. result = ";
            wcout << previousTask.get() << endl;
        }
        catch (const exception&)
        {
            wcout << L"<exception>" << endl;
        }
    }).wait();
}
/* Output:
    In first task. n = 1
    In second task. n = 2
    In third task. n = 4
    In final task. result = <exception>
*/

可以使用 concurrency::task_completion_event::set_exception 方法将异常与任务完成事件相关联。 任务并行性文档更详细地描述了 concurrency::task_completion_event 类。

concurrency::task_canceled 是一个重要的运行时异常类型,与 task 相关。 如果你调用 task::get 但该任务被取消,则运行时会引发 task_canceled。 相反,task::wait 会返回 task_status::canceled 并且不会引发,你可以通过基于任务的延续或在调用 task::get 时捕获并处理此异常。 

切勿从代码中引发 task_canceled。 改为调用 concurrency::cancel_current_task。

如果任务发生异常并且该异常未被任务、其延续任务之一或主要应用捕获,则运行时将终止应用。 如果你的应用程序崩溃,你可以将 Visual Studio 配置为在引发 C++ 异常时中断。 诊断出未处理异常的位置后,使用基于任务的延续来处理它。

  • 19
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值