Community
cancel
Showing results for 
Search instead for 
Did you mean: 
isnork
Beginner
88 Views

TBB Task scheduling + Factory pattern + Exceptions

Jump to solution
Hi,
I'm trying to use the Factory pattern to create the child tasks that will be excuted in parallel.

Here is the code for the base class, from which RootTask, ChildTask and ContinuationTask will be derived:
[cpp]#ifndef MYTASK_HPP
#define MYTASK_HPP

#include "tbb/task.h"

#include
#include
#include 

class MyTask: public tbb::task {
private:
  std::string name;
public:
  MyTask(const std::string& n):name(n){}
  template
  void AddToName( const T& to_append ) {
    std::ostringstream s( std::ostringstream::out );
    s << "-" << to_append;
    name += s.str();
  }
  virtual const std::string& Name(void) const { return name; }
  virtual tbb::task* execute(void) = 0;
};

#endif[/cpp]


Now, the code for ChildTask, ContinuationTask and the Factory that will create the tasks:

[cpp]#ifndef CHILD_HPP
#define CHILD_HPP

#include "MyTask.hpp"

#include 
#include 

class ChildTask: public MyTask {
  private:
        int num;
  public:
    ChildTask(const std::string& s, const int& n):MyTask("Child"),num(n){
      MyTask::AddToName(s);
      MyTask::AddToName(num);
    }
    const int Num(void) const { return num; }
    tbb::task* execute(void) { std::cout< 
[cpp]#ifndef CONT_HPP
#define CONT_HPP

#include "MyTask.hpp"

class ContinuationTask: public MyTask {
    public:
        ContinuationTask():MyTask("Cont"){}
        tbb::task* execute() {
            printf("Continuation-end\n");
            return NULL;
        }
};

#endif[/cpp]

[cpp]#ifndef FACTORY_HPP
#define FACTORY_HPP

#include "MyTask.hpp"
#include "ChildTask.hpp"
#include "ContinuationTask.hpp"

class Factory {
public:
  static MyTask& CreateTask(ContinuationTask* cont, int num) throw(std::exception){
      if( num<10 ){
        if( num % 2 == 0 ) {
            return (*new ( cont->allocate_child() ) ChildTask("Even",num));     
        } else {
            return (*new ( cont->allocate_child() ) ChildTask("Odd",num));     
        }
      }
      throw std::exception();
   }
};

#endif[/cpp]
Ok. Now the code for RootTask:

[cpp]#ifndef ROOT_HPP
#define ROOT_HPP

#include "MyTask.hpp"

#include "ChildTask.hpp"
#include "ContinuationTask.hpp"
#include "Factory.hpp"

class RootTask: public MyTask {
   public:
    RootTask():MyTask("Root"){}
    tbb::task* execute(void) {
             tbb::task_list list;
             ContinuationTask& cont = *new ( allocate_continuation() ) ContinuationTask();
             unsigned int i(0);
                 for( ; i<11; ++i ) {
                    try {
                     list.push_back(  Factory::CreateTask(&cont, i) ); 
                    } catch(const std::exception& e ){
                          std::cout << "What happened: " << e.what() << std::endl;
                          throw e;
                    }
                 }
             cont.set_ref_count(i);
cont.spawn( list ); return NULL; } }; And the main function:
#include "RootTask.hpp"

int main(void){
    try{
      RootTask& r = *new ( tbb::task::allocate_root() ) RootTask();
      tbb::task::spawn_root_and_wait( r ); 
    } catch(...){
      std::cout << "ABORTED\n";
    }
          return 0;   
}

[/cpp]

#endif

[cpp]The factory throws an exception since there is a case in which i is be more than 10. However, the execution of the program doesn't abort.
The console shows What happened: std::exception as expected but the pointer keeps blinking, as if doing more work and never prints the
ABORTED statement.

Shouldn't the exception stop all work and finish execution, printing the ABORTED message?

Thanks in advance.[/cpp]

0 Kudos
1 Solution
Alexey_K_Intel3
Employee
88 Views

spawn_root_and_wait() assigns a dummy parent task to the root task, and uses it to check for completion of the algorithm: when the reference counter of this dummy parent indicates that its single child is completed, the function returns. In your case, it will wait forever because:
- when you allocate a continuation for the root task, the reference to the dummy parent is transferred from the root to the continuation.
- the root task throws *before* spawning child tasks which it has allocated (and which reference the continuation task as their parent).
- since tasks were not spawned, the TBB scheduler is unaware of them. It only destroys the throwing(root) task, which by this moment has transferred its reference to the dummy parent, so it does NOT end waiting. And since no other tasks were spawned, the scheduler has nothing to execute or steal, and will wait forever.

I understand it's not the behavior you expect, but TBB task objects never had user friendly API, as their primary purpose is to build efficient higher-level algorithms. Also, be aware that task_list is not anymore an efficient mechanism for spawning several tasks at once (which it used to be before TBB 2.2). In fact, we should have better deprecated it.

There are different ways how you can handle the issue:
- before throwing the exception in the root task, set the reference count for continuation to the number of children already in the task list,and spawn the task list. This way the TBB scheduler becomes aware of all allocated tasks and will correctly destroy everything, decrement all reference counters, etc.; then the waiting function will return without execution of any task but the root.
- before throwing the exception, clean everything up: destroy all the tasks in the list, decrement the reference count of the continuation's parent, and destroy the continuation.
The second way certainly requires more coding, but it will result in less run-time overhead because the scheduler will not do redundant work of spawning tasks to a pool and taking them back from there.

View solution in original post

2 Replies
Alexey_K_Intel3
Employee
89 Views

spawn_root_and_wait() assigns a dummy parent task to the root task, and uses it to check for completion of the algorithm: when the reference counter of this dummy parent indicates that its single child is completed, the function returns. In your case, it will wait forever because:
- when you allocate a continuation for the root task, the reference to the dummy parent is transferred from the root to the continuation.
- the root task throws *before* spawning child tasks which it has allocated (and which reference the continuation task as their parent).
- since tasks were not spawned, the TBB scheduler is unaware of them. It only destroys the throwing(root) task, which by this moment has transferred its reference to the dummy parent, so it does NOT end waiting. And since no other tasks were spawned, the scheduler has nothing to execute or steal, and will wait forever.

I understand it's not the behavior you expect, but TBB task objects never had user friendly API, as their primary purpose is to build efficient higher-level algorithms. Also, be aware that task_list is not anymore an efficient mechanism for spawning several tasks at once (which it used to be before TBB 2.2). In fact, we should have better deprecated it.

There are different ways how you can handle the issue:
- before throwing the exception in the root task, set the reference count for continuation to the number of children already in the task list,and spawn the task list. This way the TBB scheduler becomes aware of all allocated tasks and will correctly destroy everything, decrement all reference counters, etc.; then the waiting function will return without execution of any task but the root.
- before throwing the exception, clean everything up: destroy all the tasks in the list, decrement the reference count of the continuation's parent, and destroy the continuation.
The second way certainly requires more coding, but it will result in less run-time overhead because the scheduler will not do redundant work of spawning tasks to a pool and taking them back from there.

View solution in original post

isnork
Beginner
88 Views
Thanks Alexey! I used the second approach you suggested and it worked perfectly!
Reply