Back to Taskflow

Create a Task Group

docs/TaskGroup.html

4.1.013.3 KB
Original Source

| | Taskflow: A General-purpose Task-parallel Programming System |

Loading...

Searching...

No Matches

Task Group

A task group is a lightweight mechanism in Taskflow to spawn and manage a collection of asynchronous tasks cooperatively within a single executor. Task groups allow tasks to be executed recursively, asynchronously, or with dependencies, enabling efficient implementation of recursive parallel algorithms.

Create a Task Group

A task group (tf::TaskGroup) is created from a worker in an executor using tf::Executor::task_group(). Since task groups rely on cooperative execution, they must be created inside a task that is already running on the executor. For example, the code below creates a task group from an asynchronous task:

tf::Executor executor;

executor.silent_async(&{

tf::TaskGroup tg = executor.task_group();

});

tf::Executor

class to create an executor

Definition executor.hpp:62

tf::Executor::silent_async

void silent_async(P &&params, F &&func)

similar to tf::Executor::async but does not return a future object

tf::Executor::task_group

TaskGroup task_group()

creates a task group that executes a collection of asynchronous tasks

Definition task_group.hpp:875

tf::TaskGroup

class to create a task group from a task

Definition task_group.hpp:61

Internally, a task group is bound to the executor and the worker that creates it. This worker is referred to as the parent worker of the task group and is the only worker allowed to issue cooperative execution (tf::TaskGroup::corun) on that task group. Attempting to create a task group from a non-worker thread will result in an exception. This restriction ensures that task groups can safely participate in the executor's work-stealing loop and enables efficient cooperative execution while preserving the execution context required for recursion.

tf::Executor executor;

tf::TaskGroup tg = executor.task_group(); // throws

Submit Asynchronous Tasks with Cooperative Execution

tf::TaskGroup supports submitting asynchronous tasks that execute cooperatively with other workers in the same executor. All tasks submitted to a task group are logically grouped and can be explicitly synchronized using tf::TaskGroup::corun(). The task group provides four categories of asynchronous submission APIs:

Each variant serves a distinct purpose depending on whether you need, including a returned future, dependency ordering between tasks, etc. For instance, the code below creates 100 tasks using tf::TaskGroup::silent_async and one task using tf::TaskGroup::async, followed by a tf::TaskGroup::corun() to cooperatively execute all tasks in the task group until every task has completed:

executor.async(&{

tf::TaskGroup tg = executor.task_group();

std::atomic<int> counter{0};

// spawn 100 silent-async tasks (without future return)

for(int i=0; i<100; i++) {

tg.silent_async(&{ counter++; });

}

// spawn one async task (with future return)

auto fu = tg.async({ return 42; });

// cooperatively run all tasks in the group

tg.corun();

assert(counter == 100);

assert(fu.get() == 42);

});

tf::Executor::async

auto async(P &&params, F &&func)

creates a parameterized asynchronous task to run the given function

tf::TaskGroup::corun

void corun()

corun all tasks spawned by this task group with other workers

Definition task_group.hpp:725

tf::TaskGroup::async

auto async(F &&f)

runs the given callable asynchronously

Definition task_group.hpp:775

tf::TaskGroup::silent_async

void silent_async(F &&f)

runs the given function asynchronously without returning any future object

Definition task_group.hpp:756

If you need dependencies among async tasks, use tf::TaskGroup::dependent_async or tf::TaskGroup::silent_dependent_async. For instance, the task group below builds a dynamic task graph of three tasks, A, B, and C, where C runs after A and B.

executor.async(&{

auto tg = executor.task_group();

tf::AsyncTask A = tg.silent_dependent_async({ printf("A\n"); });

tf::AsyncTask B = tg.silent_dependent_async({ printf("B\n"); });

tf::AsyncTask C = tg.silent_dependent_async({ printf("C\n"); }, A, B);

tg.corun();

});

tf::AsyncTask

class to hold a dependent asynchronous task with shared ownership

Definition async_task.hpp:45

tf::TaskGroup::silent_dependent_async

tf::AsyncTask silent_dependent_async(F &&func, Tasks &&... tasks)

runs the given function asynchronously when the given predecessors finish

Definition task_group.hpp:795

Cancel a Task Group

You can mark a task group as cancelled to stop any not-yet-started tasks in the group from running. Tasks that are already running will continue to completion, but no new tasks belonging to the task group will be scheduled after cancellation. The example below demonstrates how tf::TaskGroup::cancel() prevents pending tasks in a task group from executing , while allowing already running tasks to complete cooperatively. The first set of tasks deliberately occupies all but one worker thread, ensuring that subsequently spawned tasks remain pending. After invoking tf::TaskGroup::cancel(), these pending tasks are never scheduled, even after the blocked workers are released. A final call to tf::TaskGroup::corun() synchronizes with all tasks in the group, guaranteeing safe completion and verifying that cancellation successfully suppresses task execution.

const size_t W = 12; // must be >1 for this example to work

tf::Executor executor(W);

executor.async(&executor, W{

auto tg = executor.task_group();

// deliberately block the other W-1 workers

std::atomic<size_t> latch(0);

for(size_t i=0; i<W-1; ++i) {

tg.async(&{

++latch;

while(latch != 0);

});

}

// wait until the other W-1 workers are blocked

while(latch != W-1);

// spawn other tasks which should never run after cancellation

for(size_t i=0; i<100; ++i) {

tg.async(&{ throw std::runtime_error("this should never run"); });

}

// cancel the task group and unblock the other W-1 workers

assert(tg.is_cancelled() == false);

tg.cancel();

assert(tg.is_cancelled() == true);

latch = 0;

tg.corun();

});

tf::TaskGroup::cancel

void cancel()

cancel all tasks in this task group

Definition task_group.hpp:736

tf::TaskGroup::is_cancelled

bool is_cancelled()

queries if the task group has been cancelled

Definition task_group.hpp:741

Note that cancellation is cooperative: tasks should not assume immediate termination. Users must still call tf::TaskGroup::corun() to synchronize with all spawned tasks and ensure safe completion or cancellation. Failing to do so results in undefined behavior.

Implement Recursive Task Parallelism

tf::TaskGroup is particularly well suited for implementing recursive task parallelism, where tasks dynamically spawn additional tasks during execution. Because task groups support cooperative execution via tf::TaskGroup::corun(), the worker thread can preserve its execution context across recursive calls. This design makes task groups a powerful choice for parallelizing recursive algorithms, such as divide-and-conquer, tree traversal, and dynamic programming. The example below demonstrates how to implement a parallel Fibonacci algorithm using a task group:

tf::Executor executor;

size_t fibonacci(size_t N) {

if(N < 2) return N;

size_t res1, res2;

tf::TaskGroup tg = executor.task_group();

tg.silent_async(N, &res1{ res1 = fibonacci(N-1); });

res2 = fibonacci(N-2);

// cooperatively run tasks until all tasks spawned by tg complete

tg.corun();

return res1 + res2;

}

int main() {

size_t N = 30, res;

res = executor.async({ return fibonacci(30); }).get();

std::cout << N << "-th Fibonacci number is " << res << '\n';

return 0;

}

The function fibonacci spawns one recursive call as an asynchronous task and computes the other directly. Calling tf::TaskGroup::corun() ensures the asynchronous branch completes before the results are combined, while allowing the current worker to cooperatively execute spawned tasks and preserve its execution context.