Exception Handling and Cancellation in TBB - Part II – Basic use cases

After the long-winded introduction let’s consider the semantics of task cancellation and exception handling in TBB. The basic usage model of cancellation was shaped in order to cover the following primary use cases:

    1. Cancelling an algorithm when one of its tasks decides that the purpose of the algorithm has been reached. A variety of parallel search algorithms falls in this category for instance.

    1. Cancelling a lengthy processing in response to an external signal. For example a broad range of interactive applications may benefit from such a functionality.

    1. Exception handling. As I noted before any exception that escapes a task and gets caught by the TBB scheduler causes an internal cancellation request for the guilty task’s tree (it normally does not make sense to continue executing the algorithm when something went so badly wrong with one of its tasks).

Of course TBB cancellation semantics extends to more complex cases too, but everything in its own time. A common element of all these scenarios is the fact that tasks being executed as part of a parallel algorithm form a logical group that has to be cancelled as a whole as soon as any of its tasks is cancelled. Since TBB scheduler did not originally have means to maintain information about this kind of relation between tasks, new class has been introduced – tbb::task_group_context.

Now all task objects are associated with some context object. Logically related tasks are associated with the same context and thus they form a cancellation group. TBB algorithms in particular bundle the task they internally create in such groups. When a task is cancelled, all the other tasks from its group are cancelled as well. A task can cancel itself or other task by calling instance method task::cancel_group_execution(). The method’s name stresses the fact that it is impossible to cancel a single task – cancelling a task always cancels the whole group that the victim task belongs to. Here is an example

class my_search_task : public tbb::task {
// ...
tbb::task* execute () {
if ( search_my_chunk_of_data() == true ) {
// We are done! Cancel all our siblings.
return NULL;

This snippet shows in particular that in order to cancel a group of tasks from inside of it you do not need to have a direct access to the corresponding context object. It is the responsibility of the code that launches a task hierarchy to supply it with an appropriate context.

Moreover, your application can easily get by without having to explicitly manipulate contexts at all. In particular your old code will continue working without a word added. And if your new applications do not need any fancy cancellation behavior, you may safely forget about contexts. I’ll get into more detail about building task groups later.

Now that we’ve seen the simplest example of cancellation, I have to say a few more words in order to clarify what exactly happens when a task group gets cancelled. First of all, as contradictory as it sounds, the task on which the cancel_group_execution() method was called continues its execution. The same is true for any other task already being executed (being inside its execute() method) at the moment of cancellation. In other words we interrupt politely, that is never even try to terminate your (or our own) code because the modern platforms do not allow to do it safely. Hey, where is the cancellation then? None of the tasks that have been spawned and wait in TBB task pools get a chance to start after their group was cancelled. This includes the new tasks that may be spawned by the tasks that were running at the moment of cancellation.

This approach seems to be reasonable because TBB is intended to exploit and is the most efficient with fine grained parallelism, and a typical parallel algorithm usually creates at least a few hundreds of tasks to ensure optimal load balancing. Thus letting a few (at most equal to the number of available cores) tasks to safely finish their work looks like affordable price for avoiding both perils of forceful termination and complications (for both of us and you) of safe points implementation.

Besides, thanks to our prudent eschewing of any extravagance in the implementation :), we were able to keep the performance impact of all the new functionality (both cancellation and exception handling) inside the magnitude of our measurements noise (at least for typical workloads we checked against).

OK. Let’s return to the task group contexts. They also came handy in providing interface for external cancellation. Consider the use case 2 above. Interactive applications usually have a GUI thread that receives and responds to messages from the user. If the user requests a lengthy operation, it is performed by a background thread that for example starts tbb::parallel_for algorithm. But the GUI thread does not have any access to the tasks created as part of a TBB algorithm execution. Well, it is of course possible for TBB users to implement some mechanism of communicating cancellation request to the running body, but this is not a solution one could call elegant.

Instead the main GUI thread can simply create an instance of task group context, and then use it in the background thread to run the parallel algorithm. When GUI receives cancel request from the user the context is used to cancel the algorithm. Here is the sketch of how the real code could look like:

class my_app_data_t {
tbb::task_group_context my_context;
tbb::blocked_range my_huge_range;
my_processing_body_t my_body;

void processing_thread_proc ( void* arg ) {
my_app_data_t *data = (my_app_data_t*)arg;
tbb::parallel_for (data->my_huge_range, data->my_body,
tbb::auto_partitioner(), data->my_context);
if ( data->my_context.is_group_execution_cancelled() )

class my_frame_window {
my_app_data_t *my_app_data;
// . . .
void on_button_start () {
_beginthreadex(processing_thread_proc, 0, my_app_data);
void on_button_cancel () {

This snippet demonstrates the usage of two other context’s methods: is_group_execution_cancelled() and reset(). Normally the reset() method have to be called only if the context object is intended to be reused.

At last contexts are indispensable for our mechanism of exceptions propagation, but we’ll talk about it the next time. In the next post we’ll also talk about the use cases involving nested parallelism, different flavors of contexts, and how one creates them and associates with the task objects. Stay tuned!

For more complete information about compiler optimizations, see our Optimization Notice.