Refining Tasks¶
It is possible to create a sub-graph inside a task during its execution.
This is done without further thought by just calling emplace_task()
inside another task.
Either you always capture the manager by reference or create a singleton (See Singleton for Manager).
mgr.emplace_task(
[&mgr]
{
mgr.emplace_task(
[]{ /* ... */ },
TaskProperties::Builder().label("Child Task")
);
},
TaskProperties::Builder().label("Parent Task")
);
Property Constraints¶
Because the properties of the parent task already made decisions about the scheduling, any child tasks are not allowed to revert these assumptions. So the properties of child tasks are constrained and assertet at task creation. This is implemented by the EnqueuePolicy. In case of using the predefined ResourceEnqueuePolicy, it asserts the resource accesses of the parent task to be supersets of its child tasks. That means firstly no new resources should be introduced and secondly all access modes must be less or equally “mutable”, e.g. a child task cannot write a resource that is only read by the parent task.
Note
Not meeting the resource constraint will throw an exception when calling emplace_task()
. This is only possible because we don’t use access guards in this example.
rg::Resource< rg::access::IOAccess > r1;
mgr.emplace_task(
[&mgr, r1]
{
// OK.
mgr.emplace_task(
[]{ /* ... */ },
TaskProperties::Builder()
.label("good child")
.resources({ r1.make_access(rg::access::IOAccess::read) })
);
// throws runtime error
mgr.emplace_task(
[]{ /* ... */ },
TaskProperties::Builder()
.label("bad child")
.resources({ r1.make_access(rg::access::IOAccess::write) })
);
},
TaskProperties::Builder()
.label("Parent Task")
.resources({ r1.make_access(rg::access::IOAccess::read) })
);
Resource Scopes¶
It is also possible to create resources which exist locally inside a task and are only relevant for sub-tasks.
rg::IOResource< int > r1;
mgr.emplace_task(
[&mgr]( auto r1 )
{
rg::IOResource< int > local_resource;
mgr.emplace_task(
[]( auto r1, auto r2 ){ /* ... */ },
TaskProperties::Builder().label("Child Task 1"),
r1.read(),
// use local_resource here without violating the subset constraint
local_resource.write(),
);
mgr.emplace_task(
[]( auto r ){ /* ... */ },
TaskProperties::Builder().label("Child Task 2"),
local_resource.read()
);
},
TaskProperties::Builder().label("Parent Task")
// can't and doesn't need local_resource
r1.read()
);
Note
The context in which the constructor of a resource is called determines its scope-level. Local resources should therefore be constructed inside of the parent task.