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