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

Loading...
Searching...
No Matches
Public Member Functions | Friends | List of all members
tf::TaskGroup Class Reference

class to create a task group from a task More...

`#include <taskflow/core/task_group.hpp>`

## Public Member Functions
| TaskGroup (const TaskGroup &)=delete
| disabled copy constructor
| TaskGroup (TaskGroup &&)=delete
| disabled move constructor
TaskGroup & | operator= (TaskGroup &&)=delete
| disabled copy assignment
TaskGroup & | operator= (const TaskGroup &)=delete
| disabled move assignment
Executor & | executor ()
| obtains the executor that creates this task group
template<typename F>
auto | async (F &&f)
| runs the given callable asynchronously
template<typename P, typename F>
auto | async (P &&params, F &&f)
| runs the given callable asynchronously
template<typename F>
void | silent_async (F &&f)
| runs the given function asynchronously without returning any future object
template<typename P, typename F>
void | silent_async (P &&params, F &&f)
| runs the given function asynchronously without returning any future object
template<typename F, typename... Tasks> requires (std::same_as<std::decay_t<Tasks>, AsyncTask> && ...)
auto | dependent_async (F &&func, Tasks &&... tasks)
| runs the given function asynchronously when the given predecessors finish
template<TaskParamsConcept P, typename F, typename... Tasks> requires (std::same_as<std::decay_t<Tasks>, AsyncTask> && ...)
auto | dependent_async (P &&params, F &&func, Tasks &&... tasks)
| runs the given function asynchronously when the given predecessors finish
template<typename F, typename I> requires (!std::same_as<std::decay_t<I>, AsyncTask>)
auto | dependent_async (F &&func, I first, I last)
| runs the given function asynchronously when the given range of predecessors finish
template<TaskParamsConcept P, typename F, typename I> requires (!std::same_as<std::decay_t<I>, AsyncTask>)
auto | dependent_async (P &&params, F &&func, I first, I last)
| runs the given function asynchronously when the given range of predecessors finish
template<typename F, typename... Tasks> requires (std::same_as<std::decay_t<Tasks>, AsyncTask> && ...)
tf::AsyncTask | silent_dependent_async (F &&func, Tasks &&... tasks)
| runs the given function asynchronously when the given predecessors finish
template<TaskParamsConcept P, typename F, typename... Tasks> requires (std::same_as<std::decay_t<Tasks>, AsyncTask> && ...)
tf::AsyncTask | silent_dependent_async (P &&params, F &&func, Tasks &&... tasks)
| runs the given function asynchronously when the given predecessors finish
template<typename F, typename I> requires (!std::same_as<std::decay_t<I>, AsyncTask>)
tf::AsyncTask | silent_dependent_async (F &&func, I first, I last)
| runs the given function asynchronously when the given range of predecessors finish
template<TaskParamsConcept P, typename F, typename I> requires (!std::same_as<std::decay_t<I>, AsyncTask>)
tf::AsyncTask | silent_dependent_async (P &&params, F &&func, I first, I last)
| runs the given function asynchronously when the given range of predecessors finish
void | corun ()
| corun all tasks spawned by this task group with other workers
void | cancel ()
| cancel all tasks in this task group
bool | is_cancelled ()
| queries if the task group has been cancelled
size_t | size () const
| queries the number of tasks currently in this task group

## Friends
class | Executor

## Detailed Description

class to create a task group from a task

A task group executes a group of asynchronous tasks. It enables asynchronous task spawning, cooperative execution among worker threads, and naturally supports recursive parallelism. Due to cooperative execution, a task group can only be created by an executor worker; otherwise an exception will be thrown. The code below demonstrates how to use task groups to implement recursive Fibonacci parallelism.

tf::Executor executor;
size_t fibonacci(size_t N) {
if (N < 2) return N;
size_t res1, res2;
// Create a task group from the current executor
tf::TaskGroup tg = get_executor().task_group();
// Submit asynchronous tasks to the group
tg.silent_async([N, &res1](){ res1 = fibonacci(N-1); });
res2 = fibonacci(N-2); // compute one branch synchronously
// Wait for all tasks in the group to 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;
}
tf::Executor
class to create an executor
Definition executor.hpp:62
tf::Executor::async
auto async(P &&params, F &&func)
creates a parameterized asynchronous task to run the given function
tf::TaskGroup
class to create a task group from a task
Definition task_group.hpp:61
tf::TaskGroup::corun
void corun()
corun all tasks spawned by this task group with other workers
Definition task_group.hpp:725
tf::TaskGroup::executor
Executor & executor()
obtains the executor that creates this task group
Definition task_group.hpp:720
tf::TaskGroup::silent_async
void silent_async(F &&f)
runs the given function asynchronously without returning any future object
Definition task_group.hpp:756

Users must explicitly call tf::TaskGroup::corun() to ensure that all tasks have completed or been properly canceled before leaving the scope of a task group. Failing to do so results in undefined behavior.

Note
  To understand how Taskflow schedules a task group, please refer to Task Group.

## Member Function Documentation

## ◆ async() [1/2]

template<typename F>

auto tf::TaskGroup::async | ( | F && | f | ) |

runs the given callable asynchronously

Template Parameters

  F | callable type

Parameters

  f | callable object

This method creates an asynchronous task that executes the given function with the specified arguments. Unlike tf::Executor::async, the task created here is parented to the task group, where applications can issue tf::TaskGroup::corun to explicitly wait for all asynchronous tasks spawned from the task group to complete. For example:

executor.silent_async([&](){
std::atomic<int> counter(0);
auto tg = executor.task_group();
auto fu1 = tg.async([&](){ counter++; });
auto fu2 = tg.async([&](){ counter++; });
fu1.get();
fu2.get();
assert(counter == 2);
// spawn 100 asynchronous tasks from the task group
for(int i=0; i<100; i++) {
tg.silent_async([&](){ counter++; });
}
// corun until the 100 asynchronous tasks have completed
tg.corun();
assert(counter == 102);
// do something else afterwards ...
});

## ◆ async() [2/2]

template<typename P, typename F>

auto tf::TaskGroup::async | ( | P && | params,
|  | F && | f )

runs the given callable asynchronously

Template Parameters

  F | callable type
  P | task parameters type satisfying tf::TaskParamsConcept

Parameters

  params | task parameters
  f | callable

Similar to tf::TaskGroup::async, but takes a parameter of type tf::TaskParams to initialize the asynchronous task.

executor.silent_async([&](){
auto tg = executor.task_group();
auto future = tg.async("my task", [](){ return 10; });
assert(future.get() == 10);
});

## ◆ cancel()

void tf::TaskGroup::cancel | ( |  | ) |
| inline

cancel all tasks in this task group

Marks the 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.

This 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();
});

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.

## ◆ corun()

void tf::TaskGroup::corun | ( |  | ) |
| inline

corun all tasks spawned by this task group with other workers

Coruns all tasks spawned by this task group cooperatively with other workers in the same executor until all these tasks finish. Under cooperative execution, a worker is not preempted. Instead, it continues participating in the work-stealing loop, executing available tasks alongside other workers.

executor.silent_async([&](){
auto tg = executor.task_group();
std::atomic<size_t> counter{0};
// spawn 100 async tasks and wait
for(int i=0; i<100; i++) {
tg.silent_async([&](){ counter++; });
}
tg.corun();
assert(counter == 100);
// spawn another 100 async tasks and wait
for(int i=0; i<100; i++) {
tg.silent_async([&](){ counter++; });
}
tg.corun();
assert(counter == 200);
});

Note that only the parent worker of this task group (the worker who creates it) can call this corun.

## ◆ dependent_async() [1/4]

template<typename F, typename I>
requires (!std::same_as<std::decay_t<I>, AsyncTask>)

auto tf::TaskGroup::dependent_async | ( | F && | func,
|  | I | first,
|  | I | last )

runs the given function asynchronously when the given range of predecessors finish

Template Parameters

  F | callable type
  I | iterator type

Parameters

  func | callable object
  first | iterator to the beginning (inclusive)
  last | iterator to the end (exclusive)

Returns
  a pair of a tf::AsyncTask handle and a std::future that holds the result of the execution

The example below creates three asynchronous tasks, `A`, `B`, and `C`, in which task `C` runs after task `A` and task `B`. Task `C` returns a pair of its tf::AsyncTask handle and a std::future<int> that eventually will hold the result of the execution.

executor.silent_async([](){
auto tg = executor.task_group();
std::array<tf::AsyncTask, 2> array {
tg.silent_dependent_async([](){ printf("A\n"); }),
tg.silent_dependent_async([](){ printf("B\n"); })
};
auto [C, fuC] = tg.dependent_async(
[](){
printf("C runs after A and B\n");
return 1;
},
array.begin(), array.end()
);
fuC.get(); // C finishes, which in turns means both A and B finish,
// so we don't need tg.corun()
});

## ◆ dependent_async() [2/4]

template<typename F, typename... Tasks>
requires (std::same_as<std::decay_t<Tasks>, AsyncTask> && ...)

auto tf::TaskGroup::dependent_async | ( | F && | func,
|  | Tasks &&... | tasks )

runs the given function asynchronously when the given predecessors finish

Template Parameters

  F | callable type
  Tasks | tasks of type tf::AsyncTask

Parameters

  func | callable object
  tasks | asynchronous tasks on which this execution depends

Returns
  a pair of a tf::AsyncTask handle and a std::future that holds the result of the execution

The example below creates three asynchronous tasks, `A`, `B`, and `C`, in which task `C` runs after task `A` and task `B`. Task `C` returns a pair of its tf::AsyncTask handle and a std::future<int> that eventually will hold the result of the execution.

executor.silent_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"); });
auto [C, fuC] = tg.dependent_async(
[](){
printf("C runs after A and B\n");
return 1;
},
A, B
);
fuC.get(); // C finishes, which in turns means both A and B finish,
// so we don't need tg.corun()
});
tf::AsyncTask
class to hold a dependent asynchronous task with shared ownership
Definition async_task.hpp:45

## ◆ dependent_async() [3/4]

template<TaskParamsConcept P, typename F, typename I>
requires (!std::same_as<std::decay_t<I>, AsyncTask>)

auto tf::TaskGroup::dependent_async | ( | P && | params,
|  | F && | func,
|  | I | first,
|  | I | last )

runs the given function asynchronously when the given range of predecessors finish

Template Parameters

  P | task parameters type satisfying tf::TaskParamsConcept
  F | callable type
  I | iterator type

Parameters

  params | task parameters
  func | callable object
  first | iterator to the beginning (inclusive)
  last | iterator to the end (exclusive)

Returns
  a pair of a tf::AsyncTask handle and a std::future that holds the result of the execution

The example below creates three named asynchronous tasks, `A`, `B`, and `C`, in which task `C` runs after task `A` and task `B`. Task `C` returns a pair of its tf::AsyncTask handle and a std::future<int> that eventually will hold the result of the execution. Assigned task names will appear in the observers of the executor.

executor.silent_async([&](){
auto tg = executor.task_group();
std::array<tf::AsyncTask, 2> array {
tg.silent_dependent_async("A", [](){ printf("A\n"); }),
tg.silent_dependent_async("B", [](){ printf("B\n"); })
};
auto [C, fuC] = tg.dependent_async(
"C",
[](){
printf("C runs after A and B\n");
return 1;
},
array.begin(), array.end()
);
fuC.get(); // C finishes, which in turns means both A and B finish,
// so we don't need tg.corun()
});

## ◆ dependent_async() [4/4]

template<TaskParamsConcept P, typename F, typename... Tasks>
requires (std::same_as<std::decay_t<Tasks>, AsyncTask> && ...)

auto tf::TaskGroup::dependent_async | ( | P && | params,
|  | F && | func,
|  | Tasks &&... | tasks )

runs the given function asynchronously when the given predecessors finish

Template Parameters

  P | task parameters type satisfying tf::TaskParamsConcept
  F | callable type
  Tasks | tasks of type tf::AsyncTask

Parameters

  params | task parameters
  func | callable object
  tasks | asynchronous tasks on which this execution depends

Returns
  a pair of a tf::AsyncTask handle and a std::future that holds the result of the execution

The example below creates three named asynchronous tasks, `A`, `B`, and `C`, in which task `C` runs after task `A` and task `B`. Task `C` returns a pair of its tf::AsyncTask handle and a std::future<int> that eventually will hold the result of the execution. Assigned task names will appear in the observers of the executor.

executor.silent_async([&](){
auto tg = executor.task_group();
tf::AsyncTask A = tg.silent_dependent_async("A", [](){ printf("A\n"); });
tf::AsyncTask B = tg.silent_dependent_async("B", [](){ printf("B\n"); });
auto [C, fuC] = tg.dependent_async(
"C",
[](){
printf("C runs after A and B\n");
return 1;
},
A, B
);
fuC.get(); // C finishes, which in turns means both A and B finish,
// so we don't need tg.corun()
});

## ◆ executor()

Executor & tf::TaskGroup::executor | ( |  | ) |
| inline

obtains the executor that creates this task group

The running executor of a task group is the executor that creates the task group.

executor.silent_async([&](){
tf::TaskGroup tg = executor.task_group();
assert(&(tg.executor()) == &executor);
});

## ◆ is_cancelled()

bool tf::TaskGroup::is_cancelled | ( |  | ) |
| inline

queries if the task group has been cancelled

Returns
  `true` if the task group has been marked as cancelled or `false` otherwise

This method returns `true` if the task group has been marked as cancelled via a call to `cancel()`, and `false` otherwise.

executor.async([&](){
auto tg = executor.task_group();
assert(tg.is_cancelled() == false);
tg.cancel(true);
assert(tg.is_cancelled() == false);
});

The cancellation state reflects whether the task group is currently in a cancelled state and does not imply that all tasks have completed or been synchronized. If a task group spawns any task, users must still call `corun()` to synchronize with all spawned tasks and ensure safe completion or cancellation. Failing to do so results in undefined behavior.

## ◆ silent_async() [1/2]

template<typename F>

void tf::TaskGroup::silent_async | ( | F && | f | ) |

runs the given function asynchronously without returning any future object

Template Parameters

  F | callable type

Parameters

  f | callable

This function is more efficient than tf::TaskGroup::async and is recommended when the result of the asynchronous task does not need to be accessed via a std::future.

executor.silent_async([&](){
std::atomic<int> counter(0);
auto tg = executor.task_group();
for(int i=0; i<100; i++) {
tg.silent_async([&](){ counter++; });
}
tg.corun();
assert(counter == 100);
});

## ◆ silent_async() [2/2]

template<typename P, typename F>

void tf::TaskGroup::silent_async | ( | P && | params,
|  | F && | f )

runs the given function asynchronously without returning any future object

Template Parameters

  F | callable type

Parameters

  params | task parameters
  f | callable

Similar to tf::TaskGroup::silent_async, but takes a parameter of type tf::TaskParams to initialize the created asynchronous task.

executor.silent_async([&](){
auto tg = executor.task_group();
tg.silent_async("my task", [](){});
tg.corun();
});

## ◆ silent_dependent_async() [1/4]

template<typename F, typename I>
requires (!std::same_as<std::decay_t<I>, AsyncTask>)

tf::AsyncTask tf::TaskGroup::silent_dependent_async | ( | F && | func,
|  | I | first,
|  | I | last )

runs the given function asynchronously when the given range of predecessors finish

Template Parameters

  F | callable type
  I | iterator type

Parameters

  func | callable object
  first | iterator to the beginning (inclusive)
  last | iterator to the end (exclusive)

Returns
  a tf::AsyncTask handle

This member function is more efficient than tf::TaskGroup::dependent_async and is encouraged to use when you do not want a std::future to acquire the result or synchronize the execution. The example below creates three asynchronous tasks, `A`, `B`, and `C`, in which task `C` runs after task `A` and task `B`.

executor.silent_async([&](){
auto tg = executor.task_group();
std::array<tf::AsyncTask, 2> array {
tg.silent_dependent_async([](){ printf("A\n"); }),
tg.silent_dependent_async([](){ printf("B\n"); })
};
tg.silent_dependent_async(
[](){ printf("C runs after A and B\n"); }, array.begin(), array.end()
);
tg.corun(); // corun until all dependent-async tasks finish
});

## ◆ silent_dependent_async() [2/4]

template<typename F, typename... Tasks>
requires (std::same_as<std::decay_t<Tasks>, AsyncTask> && ...)

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

runs the given function asynchronously when the given predecessors finish

Template Parameters

  F | callable type
  Tasks | tasks of type tf::AsyncTask

Parameters

  func | callable object
  tasks | asynchronous tasks on which this execution depends

Returns
  a tf::AsyncTask handle

This member function is more efficient than tf::TaskGroup::dependent_async and is encouraged to use when you do not want a std::future to acquire the result or synchronize the execution. The example below creates three asynchronous tasks, `A`, `B`, and `C`, in which task `C` runs after task `A` and task `B`.

executor.silent_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"); });
tg.silent_dependent_async([](){ printf("C runs after A and B\n"); }, A, B);
tg.corun(); // corun until all dependent-async tasks finish
});

## ◆ silent_dependent_async() [3/4]

template<TaskParamsConcept P, typename F, typename I>
requires (!std::same_as<std::decay_t<I>, AsyncTask>)

tf::AsyncTask tf::TaskGroup::silent_dependent_async | ( | P && | params,
|  | F && | func,
|  | I | first,
|  | I | last )

runs the given function asynchronously when the given range of predecessors finish

Template Parameters

  F | callable type
  I | iterator type

Parameters

  params | tasks parameters
  func | callable object
  first | iterator to the beginning (inclusive)
  last | iterator to the end (exclusive)

Returns
  a tf::AsyncTask handle

This member function is more efficient than tf::TaskGroup::dependent_async and is encouraged to use when you do not want a std::future to acquire the result or synchronize the execution. The example below creates three asynchronous tasks, `A`, `B`, and `C`, in which task `C` runs after task `A` and task `B`. Assigned task names will appear in the observers of the executor.

executor.silent_async([&](){
auto tg = executor.task_group();
std::array<tf::AsyncTask, 2> array {
tg.silent_dependent_async("A", [](){ printf("A\n"); }),
tg.silent_dependent_async("B", [](){ printf("B\n"); })
};
tg.silent_dependent_async(
"C", [](){ printf("C runs after A and B\n"); }, array.begin(), array.end()
);
tg.corun(); // corun until all dependent-async tasks finish
});

## ◆ silent_dependent_async() [4/4]

template<TaskParamsConcept P, typename F, typename... Tasks>
requires (std::same_as<std::decay_t<Tasks>, AsyncTask> && ...)

tf::AsyncTask tf::TaskGroup::silent_dependent_async | ( | P && | params,
|  | F && | func,
|  | Tasks &&... | tasks )

runs the given function asynchronously when the given predecessors finish

Template Parameters

  F | callable type
  Tasks | tasks of type tf::AsyncTask

Parameters

  params | task parameters
  func | callable object
  tasks | asynchronous tasks on which this execution depends

Returns
  a tf::AsyncTask handle

This member function is more efficient than tf::TaskGroup::dependent_async and is encouraged to use when you do not want a std::future to acquire the result or synchronize the execution. The example below creates three asynchronous tasks, `A`, `B`, and `C`, in which task `C` runs after task `A` and task `B`. Assigned task names will appear in the observers of the executor.

executor.silent_async([&](){
auto tg = executor.task_group();
tf::AsyncTask A = tg.silent_dependent_async("A", [](){ printf("A\n"); });
tf::AsyncTask B = tg.silent_dependent_async("B", [](){ printf("B\n"); });
tg.silent_dependent_async(
"C", [](){ printf("C runs after A and B\n"); }, A, B
);
tg.corun(); // corun until all dependent-async tasks finish
});

## ◆ size()

size_t tf::TaskGroup::size | ( |  | ) | const
| inline

queries the number of tasks currently in this task group

Returns
  the number of tasks currently in this task group

This method returns the number of tasks that belong to the task group at the time of the call. The returned value represents a snapshot and may become outdated immediately, as tasks can be concurrently spawned, started, completed, or canceled while this method is executing. As a result, the value returned by `size()` should be used for informational or diagnostic purposes only and must not be relied upon for synchronization or correctness.

executor.silent_async([&](){
auto tg = executor.task_group();
assert(tg.size() == 0);
for(size_t i=0; i<1000; ++i) {
tg.silent_async([](){});
}
assert(tg.size() >= 0);
tg.corun();
});

The documentation for this class was generated from the following file:

- taskflow/core/task_group.hpp