docs/classtf_1_1Executor.html
class to create an executor
An tf::Executor manages a set of worker threads to run tasks using an efficient work-stealing scheduling algorithm.
// Declare an executor and a taskflowtf::Executor executor;tf::Taskflow taskflow;// Add three tasks into the taskflowtf::Task A = taskflow.emplace([] () { std::cout \<\< "This is TaskA\n"; });tf::Task B = taskflow.emplace([] () { std::cout \<\< "This is TaskB\n"; });tf::Task C = taskflow.emplace([] () { std::cout \<\< "This is TaskC\n"; });// Build precedence between tasksA.precede(B, C);tf::Future\<void\> fu = executor.run(taskflow);fu.wait();// block until the execution completesexecutor.run(taskflow, [](){ std::cout \<\< "end of 1 run"; }).wait();executor.run\_n(taskflow, 4);executor.wait\_for\_all();// block until all associated executions finishexecutor.run\_n(taskflow, 4, [](){ std::cout \<\< "end of 4 runs"; }).wait();executor.run\_until(taskflow, [cnt=0] () mutable { return ++cnt == 10; });
Most executor methods are thread-safe. For example, you can submit multiple taskflows to an executor concurrently from different threads, while other threads simultaneously create asynchronous tasks.
std::thread t1(&{ executor.run(taskflow); };std::thread t2(&{ executor.async([](){ std::cout \<\< "async task from t2\n"; }); });executor.async(&{ std::cout \<\< "async task from the main thread\n"; });
Executor(size_t N = std::thread::hardware_concurrency(), std::shared_ptr<WorkerInterface> wif = nullptr) explicit constructs the executor with N worker threads~Executor()destructs the executor
auto run(Taskflow& taskflow) -> tf::Future<void>runs a taskflow onceauto run(Taskflow&& taskflow) -> tf::Future<void>runs a moved taskflow once
template<typename C>
auto run(Taskflow& taskflow, C&& callable) -> tf::Future<void>runs a taskflow once and invoke a callback upon completion
template<typename C>
auto run(Taskflow&& taskflow, C&& callable) -> tf::Future<void>runs a moved taskflow once and invoke a callback upon completionauto run_n(Taskflow& taskflow, size_t N) -> tf::Future<void>runs a taskflow for N timesauto run_n(Taskflow&& taskflow, size_t N) -> tf::Future<void>runs a moved taskflow for N times
template<typename C>
auto run_n(Taskflow& taskflow, size_t N, C&& callable) -> tf::Future<void>runs a taskflow for N times and then invokes a callback
template<typename C>
auto run_n(Taskflow&& taskflow, size_t N, C&& callable) -> tf::Future<void>runs a moved taskflow for N times and then invokes a callback
template<typename P>
auto run_until(Taskflow& taskflow, P&& pred) -> tf::Future<void>runs a taskflow multiple times until the predicate becomes true
template<typename P>
auto run_until(Taskflow&& taskflow, P&& pred) -> tf::Future<void>runs a moved taskflow and keeps running it until the predicate becomes true
template<typename P, typename C>
auto run_until(Taskflow& taskflow, P&& pred, C&& callable) -> tf::Future<void>runs a taskflow multiple times until the predicate becomes true and then invokes the callback
template<typename P, typename C>
auto run_until(Taskflow&& taskflow, P&& pred, C&& callable) -> tf::Future<void>runs a moved taskflow and keeps running it until the predicate becomes true and then invokes the callback
template<typename T>
void corun(T& target)runs a target graph and waits until it completes using an internal worker of this executor
template<typename P>
void corun_until(P&& predicate)keeps running the work-stealing loop until the predicate returns truevoid wait_for_all()waits for all tasks to completeauto num_workers() const -> size_t noexceptqueries the number of worker threadsauto num_waiters() const -> size_t noexceptqueries the number of workers that are in the waiting loopauto num_queues() const -> size_t noexceptqueries the number of work-stealing queues used by the executorauto num_topologies() const -> size_tqueries the number of running topologies at the time of this callauto this_worker() -> Worker*queries pointer to the calling worker if it belongs to this executor, otherwise returns nullptrauto this_worker_id() const -> intqueries the id of the caller thread within this executor
template<typename Observer, typename... ArgsT>
auto make_observer(ArgsT && ... args) -> std::shared_ptr<Observer>constructs an observer to inspect the activities of worker threads
template<typename Observer>
void remove_observer(std::shared_ptr<Observer> observer)removes an observer from the executorauto num_observers() const -> size_t noexceptqueries the number of observers
template<typename P, typename F>
auto async(P&& params, F&& func) -> autocreates a parameterized asynchronous task to run the given function
template<typename F>
auto async(F&& func) -> autoruns a given function asynchronously
template<typename P, typename F>
void silent_async(P&& params, F&& func)similar to tf::Executor::async but does not return a future object
template<typename F>
void silent_async(F&& func)similar to tf::Executor::async but does not return a future object
template<typename F, typename... Tasks, std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr>
auto silent_dependent_async(F&& func, Tasks && ... tasks) -> tf::AsyncTaskruns the given function asynchronously when the given predecessors finish
template<typename P, typename F, typename... Tasks, std::enable_if_t<is_task_params_v<P> && all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr>
auto silent_dependent_async(P&& params, F&& func, Tasks && ... tasks) -> tf::AsyncTaskruns the given function asynchronously when the given predecessors finish
template<typename F, typename I, std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr>
auto silent_dependent_async(F&& func, I first, I last) -> tf::AsyncTaskruns the given function asynchronously when the given range of predecessors finish
template<typename P, typename F, typename I, std::enable_if_t<is_task_params_v<P> && !std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr>
auto silent_dependent_async(P&& params, F&& func, I first, I last) -> tf::AsyncTaskruns the given function asynchronously when the given range of predecessors finish
template<typename F, typename... Tasks, std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr>
auto dependent_async(F&& func, Tasks && ... tasks) -> autoruns the given function asynchronously when the given predecessors finish
template<typename P, typename F, typename... Tasks, std::enable_if_t<is_task_params_v<P> && all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr>
auto dependent_async(P&& params, F&& func, Tasks && ... tasks) -> autoruns the given function asynchronously when the given predecessors finish
template<typename F, typename I, std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr>
auto dependent_async(F&& func, I first, I last) -> autoruns the given function asynchronously when the given range of predecessors finish
template<typename P, typename F, typename I, std::enable_if_t<is_task_params_v<P> && !std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr>
auto dependent_async(P&& params, F&& func, I first, I last) -> autoruns the given function asynchronously when the given range of predecessors finishauto task_group() -> TaskGroupcreates a task group that executes a collection of asynchronous tasks
constructs the executor with N worker threads
| Parameters |
|---|
| N |
| wif |
The constructor spawns N worker threads to run tasks in a work-stealing loop. The number of workers must be greater than zero or an exception will be thrown. By default, the number of worker threads is equal to the maximum hardware concurrency returned by std::thread::hardware_concurrency.
Users can alter the worker behavior, such as changing thread affinity, via deriving an instance from tf::WorkerInterface.
destructs the executor
The destructor calls Executor::wait_for_all to wait for all submitted taskflows to complete and then notifies all worker threads to stop and join these threads.
runs a taskflow once
| Parameters |
|---|
| taskflow |
| Returns |
This member function executes the given taskflow once and returns a tf::Future object that eventually holds the result of the execution.
tf::Future\<void\> future = executor.run(taskflow);// do something elsefuture.wait();
This member function is thread-safe.
runs a moved taskflow once
| Parameters |
|---|
| taskflow |
| Returns |
This member function executes a moved taskflow once and returns a tf::Future object that eventually holds the result of the execution. The executor will take care of the lifetime of the moved taskflow.
tf::Future\<void\> future = executor.run(std::move(taskflow));// do something elsefuture.wait();
This member function is thread-safe.
runs a taskflow once and invoke a callback upon completion
| Parameters |
|---|
| taskflow |
| callable |
| Returns |
This member function executes the given taskflow once and invokes the given callable when the execution completes. This member function returns a tf::Future object that eventually holds the result of the execution.
tf::Future\<void\> future = executor.run(taskflow, [](){ std::cout \<\< "done"; });// do something elsefuture.wait();
This member function is thread-safe.
runs a moved taskflow once and invoke a callback upon completion
| Parameters |
|---|
| taskflow |
| callable |
| Returns |
This member function executes a moved taskflow once and invokes the given callable when the execution completes. This member function returns a tf::Future object that eventually holds the result of the execution. The executor will take care of the lifetime of the moved taskflow.
tf::Future\<void\> future = executor.run(std::move(taskflow), [](){ std::cout \<\< "done"; });// do something elsefuture.wait();
This member function is thread-safe.
runs a taskflow for N times
| Parameters |
|---|
| taskflow |
| N |
| Returns |
This member function executes the given taskflow N times and returns a tf::Future object that eventually holds the result of the execution.
tf::Future\<void\> future = executor.run\_n(taskflow, 2);// run taskflow 2 times// do something elsefuture.wait();
This member function is thread-safe.
runs a moved taskflow for N times
| Parameters |
|---|
| taskflow |
| N |
| Returns |
This member function executes a moved taskflow N times and returns a tf::Future object that eventually holds the result of the execution. The executor will take care of the lifetime of the moved taskflow.
tf::Future\<void\> future = executor.run\_n(std::move(taskflow), 2// run the moved taskflow 2 times);// do something elsefuture.wait();
This member function is thread-safe.
runs a taskflow for N times and then invokes a callback
| Parameters |
|---|
| taskflow |
| N |
| callable |
| Returns |
This member function executes the given taskflow N times and invokes the given callable when the execution completes. This member function returns a tf::Future object that eventually holds the result of the execution.
tf::Future\<void\> future = executor.run(taskflow, 2, [](){ std::cout \<\< "done"; }// runs taskflow 2 times and invoke// the lambda to print "done");// do something elsefuture.wait();
This member function is thread-safe.
runs a moved taskflow for N times and then invokes a callback
| Parameters |
|---|
| taskflow |
| N |
| callable |
| Returns |
This member function executes a moved taskflow N times and invokes the given callable when the execution completes. This member function returns a tf::Future object that eventually holds the result of the execution.
tf::Future\<void\> future = executor.run\_n(// run the moved taskflow 2 times and invoke the lambda to print "done"std::move(taskflow), 2, [](){ std::cout \<\< "done"; });// do something elsefuture.wait();
This member function is thread-safe.
runs a taskflow multiple times until the predicate becomes true
| Parameters |
|---|
| taskflow |
| pred |
| Returns |
This member function executes the given taskflow multiple times until the predicate returns true. This member function returns a tf::Future object that eventually holds the result of the execution.
tf::Future\<void\> future = executor.run\_until(taskflow, [](){ return rand()%10 == 0 });// do something elsefuture.wait();
This member function is thread-safe.
runs a moved taskflow and keeps running it until the predicate becomes true
| Parameters |
|---|
| taskflow |
| pred |
| Returns |
This member function executes a moved taskflow multiple times until the predicate returns true. This member function returns a tf::Future object that eventually holds the result of the execution. The executor will take care of the lifetime of the moved taskflow.
tf::Future\<void\> future = executor.run\_until(std::move(taskflow), [](){ return rand()%10 == 0 });// do something elsefuture.wait();
This member function is thread-safe.
runs a taskflow multiple times until the predicate becomes true and then invokes the callback
| Parameters |
|---|
| taskflow |
| pred |
| callable |
| Returns |
This member function executes the given taskflow multiple times until the predicate returns true and then invokes the given callable when the execution completes. This member function returns a tf::Future object that eventually holds the result of the execution.
tf::Future\<void\> future = executor.run\_until(taskflow, [](){ return rand()%10 == 0 }, [](){ std::cout \<\< "done"; });// do something elsefuture.wait();
This member function is thread-safe.
runs a moved taskflow and keeps running it until the predicate becomes true and then invokes the callback
| Parameters |
|---|
| taskflow |
| pred |
| callable |
| Returns |
This member function executes a moved taskflow multiple times until the predicate returns true and then invokes the given callable when the execution completes. This member function returns a tf::Future object that eventually holds the result of the execution. The executor will take care of the lifetime of the moved taskflow.
tf::Future\<void\> future = executor.run\_until(std::move(taskflow),[](){ return rand()%10 == 0 }, [](){ std::cout \<\< "done"; });// do something elsefuture.wait();
This member function is thread-safe.
runs a target graph and waits until it completes using an internal worker of this executor
| Template parameters |
|---|
| T |
| Parameters |
| --- |
| target |
The method coruns a target graph cooperatively with other workers in the same executor and block until the execution completes. Under cooperative execution, a worker is not preempted. Instead, it continues participating in the work-stealing loop, executing available tasks alongside other workers.
tf::Executor executor(2);tf::Taskflow taskflow;std::array\<tf::Taskflow, 1000\> others;std::atomic\<size\_t\> counter{0};for(size\_t n=0; n\<1000; n++) {for(size\_t i=0; i\<1000; i++) {others[n].emplace(&{ counter++; });}taskflow.emplace([&executor, &tf=others[n]](){executor.corun(tf);//executor.run(tf).wait(); \<- blocking the worker without doing anything// will introduce deadlock});}executor.run(taskflow).wait();
The method is thread-safe as long as the target is not concurrently ran by two or more threads.
keeps running the work-stealing loop until the predicate returns true
| Template parameters |
|---|
| P |
| Parameters |
| --- |
| predicate |
The method keeps the caller worker running in the work-stealing loop until the stop predicate becomes true.
The method keeps the calling worker running available tasks cooperatively with other workers in the same executor and block until the predicate return true. Under cooperative execution, a worker is not preempted. Instead, it continues participating in the work-stealing loop, executing available tasks alongside other workers.
taskflow.emplace(&{std::future\<void\> fu = std::async([](){ std::sleep(100s); });executor.corun\_until([](){return fu.wait\_for(std::chrono::seconds(0)) == future\_status::ready;});});
waits for all tasks to complete
This member function waits until all submitted tasks (e.g., taskflows, asynchronous tasks) to finish.
executor.run(taskflow1);executor.run\_n(taskflow2, 10);executor.run\_n(taskflow3, 100);executor.wait\_for\_all();// wait until the above submitted taskflows finish
queries the number of worker threads
Each worker represents a unique thread spawned by an executor upon its construction time.
tf::Executor executor(4);std::cout \<\< executor.num\_workers();// 4
queries the number of workers that are in the waiting loop
A worker in the waiting loop has exhausted its local queue and made enough stealing attempts, and is now ready to be preempted and enter the waiting state.
queries the number of running topologies at the time of this call
When a taskflow is submitted to an executor, a topology is created to store runtime metadata of the running taskflow. When the execution of the submitted taskflow finishes, its corresponding topology will be removed from the executor.
executor.run(taskflow);std::cout \<\< executor.num\_topologies();// 0 or 1 (taskflow still running)
queries pointer to the calling worker if it belongs to this executor, otherwise returns nullptr
Returns a pointer to the per-worker storage associated with this executor. If the calling thread is not a worker of this executor, the function returns nullptr.
auto w = executor.this\_worker();tf::Taskflow taskflow;tf::Executor executor;executor.async(&{assert(executor.this\_worker() != nullptr);assert(executor.this\_worker()-\>executor() == &executor);});
queries the id of the caller thread within this executor
Each worker has an unique id in the range of 0 to N-1 associated with its parent executor. If the caller thread does not belong to the executor, -1 is returned.
tf::Executor executor(4);// 4 workers in the executorexecutor.this\_worker\_id();// -1 (main thread is not a worker)taskflow.emplace(&{std::cout \<\< executor.this\_worker\_id();// 0, 1, 2, or 3});executor.run(taskflow);
constructs an observer to inspect the activities of worker threads
| Template parameters |
|---|
| Observer |
| ArgsT |
| Parameters |
| --- |
| args |
| Returns |
Each executor manages a list of observers with shared ownership with callers. For each of these observers, the two member functions, tf::ObserverInterface::on_entry and tf::ObserverInterface::on_exit will be called before and after the execution of a task.
This member function is not thread-safe.
removes an observer from the executor
This member function is not thread-safe.
creates a parameterized asynchronous task to run the given function
| Template parameters |
|---|
| P |
| F |
| Parameters |
| --- |
| params |
| func |
| Returns |
The method creates a parameterized asynchronous task to run the given function and return a std::future object that eventually will hold the result of the execution.
std::future\<int\> future = executor.async("name", [](){std::cout \<\< "create an asynchronous task with a name and returns 1\n";return 1;});future.get();
This member function is thread-safe.
runs a given function asynchronously
| Template parameters |
|---|
| F |
| Parameters |
| --- |
| func |
| Returns |
The method creates an asynchronous task to run the given function and return a std::future object that eventually will hold the result of the return value.
std::future\<int\> future = executor.async([](){std::cout \<\< "create an asynchronous task and returns 1\n";return 1;});future.get();
This member function is thread-safe.
similar to tf::Executor::async but does not return a future object
| Template parameters |
|---|
| F |
| Parameters |
| --- |
| params |
| func |
The method creates a parameterized asynchronous task to run the given function without returning any std::future object. This member function is more efficient than tf::Executor::async and is encouraged to use when applications do not need a std::future to acquire the result or synchronize the execution.
executor.silent\_async("name", [](){std::cout \<\< "create an asynchronous task with a name and no return\n";});executor.wait\_for\_all();
This member function is thread-safe.
similar to tf::Executor::async but does not return a future object
| Template parameters |
|---|
| F |
| Parameters |
| --- |
| func |
The method creates an asynchronous task to run the given function without returning any std::future object. This member function is more efficient than tf::Executor::async and is encouraged to use when applications do not need a std::future to acquire the result or synchronize the execution.
executor.silent\_async([](){std::cout \<\< "create an asynchronous task with no return\n";});executor.wait\_for\_all();
This member function is thread-safe.
runs the given function asynchronously when the given predecessors finish
| Template parameters |
|---|
| F |
| Tasks |
| Parameters |
| --- |
| func |
| tasks |
| Returns |
This member function is more efficient than tf::Executor::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.
tf::AsyncTask A = executor.silent\_dependent\_async([](){ printf("A\n"); });tf::AsyncTask B = executor.silent\_dependent\_async([](){ printf("B\n"); });executor.silent\_dependent\_async([](){ printf("C runs after A and B\n"); }, A, B);executor.wait\_for\_all();
This member function is thread-safe.
runs the given function asynchronously when the given predecessors finish
| Template parameters |
|---|
| F |
| Tasks |
| Parameters |
| --- |
| params |
| func |
| tasks |
| Returns |
This member function is more efficient than tf::Executor::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.
tf::AsyncTask A = executor.silent\_dependent\_async("A", [](){ printf("A\n"); });tf::AsyncTask B = executor.silent\_dependent\_async("B", [](){ printf("B\n"); });executor.silent\_dependent\_async("C", [](){ printf("C runs after A and B\n"); }, A, B);executor.wait\_for\_all();
This member function is thread-safe.
runs the given function asynchronously when the given range of predecessors finish
| Template parameters |
|---|
| F |
| I |
| Parameters |
| --- |
| func |
| first |
| last |
| Returns |
This member function is more efficient than tf::Executor::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.
std::array\<tf::AsyncTask, 2\> array {executor.silent\_dependent\_async([](){ printf("A\n"); }),executor.silent\_dependent\_async([](){ printf("B\n"); })};executor.silent\_dependent\_async([](){ printf("C runs after A and B\n"); }, array.begin(), array.end());executor.wait\_for\_all();
This member function is thread-safe.
runs the given function asynchronously when the given range of predecessors finish
| Template parameters |
|---|
| F |
| I |
| Parameters |
| --- |
| params |
| func |
| first |
| last |
| Returns |
This member function is more efficient than tf::Executor::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.
std::array\<tf::AsyncTask, 2\> array {executor.silent\_dependent\_async("A", [](){ printf("A\n"); }),executor.silent\_dependent\_async("B", [](){ printf("B\n"); })};executor.silent\_dependent\_async("C", [](){ printf("C runs after A and B\n"); }, array.begin(), array.end());executor.wait\_for\_all();
This member function is thread-safe.
runs the given function asynchronously when the given predecessors finish
| Template parameters |
|---|
| F |
| Tasks |
| Parameters |
| --- |
| func |
| tasks |
| Returns |
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.
tf::AsyncTask A = executor.silent\_dependent\_async([](){ printf("A\n"); });tf::AsyncTask B = executor.silent\_dependent\_async([](){ printf("B\n"); });auto [C, fuC] = executor.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
You can mix the use of tf::AsyncTask handles returned by tf::Executor::dependent_async and tf::Executor::silent_dependent_async when specifying task dependencies.
This member function is thread-safe.
runs the given function asynchronously when the given predecessors finish
| Template parameters |
|---|
| P |
| F |
| Tasks |
| Parameters |
| --- |
| params |
| func |
| tasks |
| Returns |
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.
tf::AsyncTask A = executor.silent\_dependent\_async("A", [](){ printf("A\n"); });tf::AsyncTask B = executor.silent\_dependent\_async("B", [](){ printf("B\n"); });auto [C, fuC] = executor.dependent\_async("C",[](){ printf("C runs after A and B\n"); return 1;}, A, B);assert(fuC.get()==1);// C finishes, which in turns means both A and B finish
You can mix the use of tf::AsyncTask handles returned by tf::Executor::dependent_async and tf::Executor::silent_dependent_async when specifying task dependencies.
This member function is thread-safe.
runs the given function asynchronously when the given range of predecessors finish
| Template parameters |
|---|
| F |
| I |
| Parameters |
| --- |
| func |
| first |
| last |
| Returns |
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.
std::array\<tf::AsyncTask, 2\> array {executor.silent\_dependent\_async([](){ printf("A\n"); }),executor.silent\_dependent\_async([](){ printf("B\n"); })};auto [C, fuC] = executor.dependent\_async([](){ printf("C runs after A and B\n"); return 1;}, array.begin(), array.end());assert(fuC.get()==1);// C finishes, which in turns means both A and B finish
You can mix the use of tf::AsyncTask handles returned by tf::Executor::dependent_async and tf::Executor::silent_dependent_async when specifying task dependencies.
This member function is thread-safe.
runs the given function asynchronously when the given range of predecessors finish
| Template parameters |
|---|
| P |
| F |
| I |
| Parameters |
| --- |
| params |
| func |
| first |
| last |
| Returns |
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.
std::array\<tf::AsyncTask, 2\> array {executor.silent\_dependent\_async("A", [](){ printf("A\n"); }),executor.silent\_dependent\_async("B", [](){ printf("B\n"); })};auto [C, fuC] = executor.dependent\_async("C",[](){ printf("C runs after A and B\n"); return 1;}, array.begin(), array.end());assert(fuC.get()==1);// C finishes, which in turns means both A and B finish
You can mix the use of tf::AsyncTask handles returned by tf::Executor::dependent_async and tf::Executor::silent_dependent_async when specifying task dependencies.
This member function is thread-safe.
creates a task group that executes a collection of asynchronous tasks
| Returns | a tf::TaskGroup object associated with the current executor |
A TaskGroup allows submitting multiple asynchronous tasks to the executor and waiting for their completion collectively using corun(). Tasks added to the group can execute in parallel and may capture local variables by value or reference, depending on your needs. This can be useful for divide-and-conquer algorithms, parallel loops, or any workflow that requires grouping related tasks.
Example (computing Fibonacci numbers in parallel):
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 executortf::TaskGroup tg = get\_executor().task\_group();// Submit asynchronous tasks to the grouptg.silent\_async(N, &res1{ res1 = fibonacci(N-1); });res2 = fibonacci(N-2);// compute one branch synchronously// Wait for all tasks in the group to completetg.corun();return res1 + res2;}int main() {return executor.async([](){ return fibonacci(30); }).get();}
This member function is thread-safe.