How should try-catch blocks be placed when your code uses OpenMP tasks? Should a try-catch block be defined inside each task or can a single try-catch block encompass all tasks? In other words consider this code:
int main()
{
#pragma omp parallel
{
#pragma omp single
{
try
{
#pragma omp task
{ /*some code here*/ }
#pragma omp task
{ /*some code here*/ }
}
catch(Exception &e)
{
//I want to do something here whenever one of the tasks throws an exception
}
}
}
}
Is the above code correct if I want to catch any exception thrown by one of the tasks, in order to lets say print an error message corresponding to the exception and abort the program? Or do I need to define a try-catch block inside each task, like this?:
int main()
{
#pragma omp parallel
{
#pragma omp single
{
#pragma omp task
{
try
{
/*some code here*/
}
catch(Exception &e)
{
//print an error message here if there is an exception in the task and abort the program
}
}
#pragma omp task
{
try
{
/*some code here*/
}
catch(Exception &e)
{
//print an error message here if there is an exception in the task and abort the program
}
}
}
}
}
Or do I need a try-catch block inside each task but also surrounding all the tasks?
The OpenMP 5.1 specification states:
When any thread encounters a task generating construct, one or more explicit tasks are generated. Execution of explicitly generated tasks is assigned to one of the threads in the current team, subject to the thread’s availability to execute work. Thus, execution of the new task could be immediate, or deferred until later according to task scheduling constraints and thread availability. Threads are allowed to suspend the current task region at a task scheduling point in order to execute a different task. If the suspended task region is for a tied task, the initially assigned thread later resumes execution of the suspended task region. If the suspended task region is for an untied task, then any thread may resume its execution. Completion of all explicit tasks bound to a given parallel region is guaranteed before the primary thread leaves the implicit barrier at the end of the region.
The specification also states:
A throw executed inside a parallel region must cause execution to resume within the same parallel region, and the same thread that threw the exception must catch it.
This means that the first code will generally not work because the runtime can execute a task later or even in another thread (waiting for work). In your example, the exception need to be catch in the inner code of all tasks in which you want to track an exception. However, it doe not means you need to copy the try-catch block in every tasks. You can define a function for that or a lambda with templates if you need to capture some local variable.
Here is an example:
// You can add more parameter to tune the error message regarding
// the code of the task. The error management could even be
// specialized another lambda.
template <typename FunType>
void catchExceptions(FunType lambda)
{
try
{
lambda();
}
catch(Exception& e)
{
// Print an error message here if there is an exception in
// the task and abort the program.
}
}
int main()
{
#pragma omp parallel
#pragma omp single
{
#pragma omp task
catchExceptions([&](){
/*some code here*/
});
#pragma omp task
catchExceptions([&](){
/*some code here*/
});
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With