【问题标题】:concurrency::task destructor causes call to abort in valid use-caseconcurrency::task 析构函数导致调用在有效用例中中止
【发布时间】:2014-07-03 16:41:47
【问题描述】:

您能否告诉我我用来处理用例的方法是否无效,如果是,正确的处理方法是什么:

task<int> do_work(int param)
{
    // runs some work on a separate thread, returns task with result or throws exception on failure
}

void foo()
{
    try
    {
        auto result_1 = do_work(10000);
        auto result_2 = do_work(20000);

        // do some extra work

        process(result_1.get(), result_2.get());
    }
    catch (...)
    {
        // logs the failure details
    }
}

所以代码尝试并行执行两个作业,然后处理结果。如果其中一项作业引发异常,则调用task::get 将重新引发异常。如果两个任务都抛出异常,则会发生此问题。在这种情况下,对task::get 的第一次调用将导致堆栈展开,因此将调用第二个task 的析构函数,并反过来导致在堆栈展开期间重新抛出一个异常,从而导致“中止”调用。

在我遇到问题之前,这种方法对我来说似乎完全有效。

【问题讨论】:

  • 为什么(第二个)任务从其析构函数中抛出?
  • @StackedCrooked... 这实际上是主要问题。要么我弄错了,要么task析构函数真的重新抛出了do_work函数中捕获的异常。
  • @Cyber​​... 我看不出它是如何相关的。你能解释一下吗?

标签: c++ concurrency-runtime


【解决方案1】:

简单来说,您有一个未处理(未观察到)的异常,因为在您的一个任务中抛出的异常不会得到caught by the task, one of its continuations, or the main app,因为第一个任务从 task::get 重新抛出的异常会展开堆栈在第二个任务调用 task::get 之前。

更简化的代码显示调用std::terminate 是因为任务中抛出的异常没有得到处理。取消注释result.get() 将阻止对std::terminate 的调用,因为task::get 将重新引发异常。

#include <pplx/pplx.h>
#include <pplx/pplxtasks.h>
#include <iostream>

int main(int argc, char* argv[])
{
    try
    {
        auto result = pplx::create_task([] ()-> int
        {
            throw std::exception("task failed");
        });

        // actually need wait here till the exception is thrown, e.g.
        // result.wait(), but this will re-throw the exception making this a valid use-case

        std::cout << &result << std::endl; // use it
        //std::cout << result.get() << std::endl;
    }
    catch (std::exception const& ex)
    {
        std::cout << ex.what() << std::endl;
    }

    return 0;
}

看看pplx::details::_ExceptionHandler::~_ExceptionHolder()中的建议

//pplxwin.h
#define _REPORT_PPLTASK_UNOBSERVED_EXCEPTION() do { \
    __debugbreak(); \
    std::terminate(); \
} while(false)


//pplxtasks.h
pplx::details::_ExceptionHandler::~_ExceptionHolder()
{
    if (_M_exceptionObserved == 0)
    {
        // If you are trapped here, it means an exception thrown in task chain didn't get handled.
        // Please add task-based continuation to handle all exceptions coming from tasks.
        // this->_M_stackTrace keeps the creation callstack of the task generates this exception.
        _REPORT_PPLTASK_UNOBSERVED_EXCEPTION();
    }
}

在原始代码中,对task::get 的第一次调用引发了该任务中引发的异常,这显然阻止了对task::get 的第二次调用,因此第二个任务的异常不会得到处理(仍然“未观察到”)。

将调用第二个任务的析构函数,并反过来导致在堆栈展开期间重新抛出一个异常,从而导致调用“中止”。

第二个任务的析构函数不会重新抛出它只是调用std::terminate()(调用std::abort())的异常

看。 Exception Handling in the Concurrency Runtime

【讨论】:

  • 我明白了,谢谢。所以析构函数不会重新抛出(这很好),它只是调用std::terminate(这实际上很糟糕并且不会改变我的问题)。问题是使用我使用的方法是否正确。在我看来,这种方法绝对有效:用户触发多个并行任务,然后获取结果并处理它们。现在我看到,如果至少有两个任务失败,它根本就无法工作......
猜你喜欢
  • 2016-10-12
  • 1970-01-01
  • 2014-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-24
  • 2016-10-15
  • 1970-01-01
相关资源
最近更新 更多