деструктор concurrency :: task вызывает прерывание вызова в допустимом сценарии использования

Не могли бы вы сказать мне, если подход, который я использую для обработки сценария использования, является недействительным, и если да, то какой правильный способ обработки:

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 будет вызван и, в свою очередь, вызовет повторное появление еще одного исключения во время размотки стека, что вызовет вызов ‘abort’.

Этот подход казался мне полностью действительным, пока я не столкнулся с проблемой.

5

Решение

Проще говоря, у вас есть необработанное (ненаблюдаемое) исключение, так как исключение, выброшенное в одной из ваших задач, не получает поймано задачей, одним из ее продолжений или основным приложением, потому что исключение, повторно выброшенное из 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 таким образом, исключение из второй задачи не обрабатывается (остается «ненаблюдаемым»).

будет вызван деструктор второй задачи, который, в свою очередь, вызовет повторное появление еще одного исключения во время размотки стека, что вызовет вызов ‘abort’.

Деструктор второй задачи не перебрасывает исключение, он просто вызывает std :: terminate () (который вызывает std :: abort ())

увидеть. Обработка исключений в параллельной среде выполнения

2

Другие решения