Prateek Chokse
Prateek Chokse

Reputation: 17

How to handle exception for parallel std algorithms

I am executing n task using std::for_each and these tasks can be canceled. So for doing that I have a flag that is set to true if tasks to be canceled which in turn throws some exception in the task's code. And it works fine if I use normal std::for_each, but it aborts if I use any of std execution_policy. Is there a way to stop my code from aborting?

#include <execution>
#include <array>
#include <chrono>
#include <exception>
using namespace std::chrono_literals;

auto main(int argc, char* argv[]) -> int {
    std::array<int, 5> x {1, 2, 3, 4, 5};
    std::atomic<bool> toBeCancelled = true;
    std::for_each(std::execution::par, std::begin(x), std::end(x), [&](const int& x) {
        std::this_thread::sleep_for(2s);
        if (toBeCancelled)
            throw std::runtime_error("taskCancelled");
    });
    return 0;
}

The number of tasks can be 1000-10000. I usually don't use exceptions, instead I cover major code in "if condition". but here task count is so large, I thought running code outside of "if condition" is not worth it.

Upvotes: 1

Views: 556

Answers (2)

KamilCuk
KamilCuk

Reputation: 141523

From cppreference execution policies:

During the execution of a parallel algorithm with any of these execution policies, if the invocation of an element access function exits via an uncaught exception, std::terminate is called, but the implementations may define additional execution policies that handle exceptions differently.

So if it throws, your program will terminate.

How to handle exception for parallel std algorithms

Then cppreference std::thread has the answer to your question:

[...] function may communicate its return value or an exception to the caller via std::promise or by modifying shared variables (which may require synchronization, see std::mutex and std::atomic).

Use a shared variable and remember about locking. There's even an example in cppreference execution policies that increments an integer, with a bit of tweaking your code may look like:

std::mutex m;
std::vector<std::exception> throwed;
std::for_each(std::execution::par_unseq, std::begin(a), std::end(a), [&](int) {
     std::this_thread::sleep_for(2s);
     {
        std::lock_guard<std::mutex> guard(m);
        if (toBeCancelled) {
            throwed.push_back(std::runtime_error("taskCancelled"));
        }
    }
});

or you can catch the exception and push_back it then.

Upvotes: 0

Caleth
Caleth

Reputation: 63039

You can't let an exception out of the callable you pass to for_each, under pain of std::terminate, as you have seen. But you don't need an exception, you know that you are cancelling tasks.

[&](const int& x) {
    std::this_thread::sleep_for(2s);
    if (toBeCancelled)
        return;
}

Aside: in C++20 we get std::stop_token which is intended for this sort of signalling.

Upvotes: 2

Related Questions