# tf::Executor class

class to create an executor for running a taskflow graph

An executor manages a set of worker threads to run one or multiple taskflows using an efficient work-stealing scheduling algorithm.

    // Declare an executor and a taskflow
    tf::Executor executor;
    tf::Taskflow taskflow;

    // Add three tasks into the taskflow
    tf::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 tasks
    A.precede(B, C);

    tf::Future<void> fu = executor.run(taskflow);
    fu.wait();                // block until the execution completes

    executor.run(taskflow, [](){ std::cout << "end of 1 run"; }).wait();
    executor.run_n(taskflow, 4);
    executor.wait_for_all();  // block until all associated executions finish
    executor.run_n(taskflow, 4, [](){ std::cout << "end of 4 runs"; }).wait();
    executor.run_until(taskflow, [cnt=0] () mutable { return ++cnt == 10; });

All the `run` methods are thread-safe. You can submit multiple taskflows at the same time to an executor from different threads.

## Constructors, destructors, conversion operators

Executor(size_t N = std::thread::hardware_concurrency(), std::shared_ptr<WorkerInterface> wix = nullptr) explicit
  constructs the executor with `N` worker threads
~Executor()
  destructs the executor

## Public functions

auto run(Taskflow& taskflow) -> tf::Future<void>
  runs a taskflow once
auto 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 completion
auto run_n(Taskflow& taskflow, size_t N) -> tf::Future<void>
  runs a taskflow for `N` times
auto 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 becomes true
void wait_for_all()
  waits for all tasks to complete
auto num_workers() const -> size_t noexcept
  queries the number of worker threads
auto num_topologies() const -> size_t
  queries the number of running topologies at the time of this call
auto num_taskflows() const -> size_t
  queries the number of running taskflows with moved ownership
auto this_worker_id() const -> int
  queries the id of the caller thread in 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 executor
auto num_observers() const -> size_t noexcept
  queries the number of observers
template<typename F>
auto async(F&& func) -> auto
  runs a given function asynchronously
template<typename F>
auto async(const std::string& name, F&& func) -> auto
  runs a given function asynchronously and gives a name to this task
template<typename F>
void silent_async(F&& func)
  similar to tf::Executor::async but does not return a future object
template<typename F>
void silent_async(const std::string& name, 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::AsyncTask
  runs the given function asynchronously when the given dependents finish
template<typename F, typename... Tasks, std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr>
auto silent_dependent_async(const std::string& name, F&& func, Tasks && ... tasks) -> tf::AsyncTask
  names and runs the given function asynchronously when the given dependents 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::AsyncTask
  runs the given function asynchronously when the given range of dependents 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(const std::string& name, F&& func, I first, I last) -> tf::AsyncTask
  names and runs the given function asynchronously when the given range of dependents 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) -> auto
  runs the given function asynchronously when the given dependents finish
template<typename F, typename... Tasks, std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr>
auto dependent_async(const std::string& name, F&& func, Tasks && ... tasks) -> auto
  names and runs the given function asynchronously when the given dependents 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) -> auto
  runs the given function asynchronously when the given range of dependents finish
template<typename F, typename I, std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr>
auto dependent_async(const std::string& name, F&& func, I first, I last) -> auto
  names and runs the given function asynchronously when the given range of dependents finish

## Function documentation

### tf::Executor::Executor(size_t N = std::thread::hardware_concurrency(), std::shared_ptr<WorkerInterface> wix = nullptr) explicit

constructs the executor with `N` worker threads

Parameters
N | number of workers (default std::thread::hardware_concurrency)
wix | worker interface class to alter worker (thread) behaviors

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.

### tf::Executor::~Executor()

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.

### tf::Future<void> tf::Executor::run(Taskflow& taskflow)

runs a taskflow once

Parameters
taskflow | a tf::Taskflow object
Returns | a tf::Future that holds the result of the execution

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 else
    future.wait();

This member function is thread-safe.

#### Attention

The executor does not own the given taskflow. It is your responsibility to ensure the taskflow remains alive during its execution.

### tf::Future<void> tf::Executor::run(Taskflow&& taskflow)

runs a moved taskflow once

Parameters
taskflow | a moved tf::Taskflow object
Returns | a tf::Future that holds the result of the execution

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 else
    future.wait();

This member function is thread-safe.

###
template<typename C>
tf::Future<void> tf::Executor::run(Taskflow& taskflow, C&& callable)

runs a taskflow once and invoke a callback upon completion

Parameters
taskflow | a tf::Taskflow object
callable | a callable object to be invoked after this run
Returns | a tf::Future that holds the result of the execution

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 else
    future.wait();

This member function is thread-safe.

#### Attention

The executor does not own the given taskflow. It is your responsibility to ensure the taskflow remains alive during its execution.

###
template<typename C>
tf::Future<void> tf::Executor::run(Taskflow&& taskflow, C&& callable)

runs a moved taskflow once and invoke a callback upon completion

Parameters
taskflow | a moved tf::Taskflow object
callable | a callable object to be invoked after this run
Returns | a tf::Future that holds the result of the execution

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 else
    future.wait();

This member function is thread-safe.

### tf::Future<void> tf::Executor::run_n(Taskflow& taskflow, size_t N)

runs a taskflow for `N` times

Parameters
taskflow | a tf::Taskflow object
N | number of runs
Returns | a tf::Future that holds the result of the execution

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 else
    future.wait();

This member function is thread-safe.

#### Attention

The executor does not own the given taskflow. It is your responsibility to ensure the taskflow remains alive during its execution.

### tf::Future<void> tf::Executor::run_n(Taskflow&& taskflow, size_t N)

runs a moved taskflow for `N` times

Parameters
taskflow | a moved tf::Taskflow object
N | number of runs
Returns | a tf::Future that holds the result of the execution

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 else
    future.wait();

This member function is thread-safe.

###
template<typename C>
tf::Future<void> tf::Executor::run_n(Taskflow& taskflow, size_t N, C&& callable)

runs a taskflow for `N` times and then invokes a callback

Parameters
taskflow | a tf::Taskflow
N | number of runs
callable | a callable object to be invoked after this run
Returns | a tf::Future that holds the result of the execution

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 else
    future.wait();

This member function is thread-safe.

#### Attention

The executor does not own the given taskflow. It is your responsibility to ensure the taskflow remains alive during its execution.

###
template<typename C>
tf::Future<void> tf::Executor::run_n(Taskflow&& taskflow, size_t N, C&& callable)

runs a moved taskflow for `N` times and then invokes a callback

Parameters
taskflow | a moved tf::Taskflow
N | number of runs
callable | a callable object to be invoked after this run
Returns | a tf::Future that holds the result of the execution

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 else
    future.wait();

This member function is thread-safe.

###
template<typename P>
tf::Future<void> tf::Executor::run_until(Taskflow& taskflow, P&& pred)

runs a taskflow multiple times until the predicate becomes true

Parameters
taskflow | a tf::Taskflow
pred | a boolean predicate to return `true` for stop
Returns | a tf::Future that holds the result of the execution

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 else
    future.wait();

This member function is thread-safe.

#### Attention

The executor does not own the given taskflow. It is your responsibility to ensure the taskflow remains alive during its execution.

###
template<typename P>
tf::Future<void> tf::Executor::run_until(Taskflow&& taskflow, P&& pred)

runs a moved taskflow and keeps running it until the predicate becomes true

Parameters
taskflow | a moved tf::Taskflow object
pred | a boolean predicate to return `true` for stop
Returns | a tf::Future that holds the result of the execution

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 else
    future.wait();

This member function is thread-safe.

###
template<typename P, typename C>
tf::Future<void> tf::Executor::run_until(Taskflow& taskflow, P&& pred, C&& callable)

runs a taskflow multiple times until the predicate becomes true and then invokes the callback

Parameters
taskflow | a tf::Taskflow
pred | a boolean predicate to return `true` for stop
callable | a callable object to be invoked after this run completes
Returns | a tf::Future that holds the result of the execution

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 else
    future.wait();

This member function is thread-safe.

#### Attention

The executor does not own the given taskflow. It is your responsibility to ensure the taskflow remains alive during its execution.

###
template<typename P, typename C>
tf::Future<void> tf::Executor::run_until(Taskflow&& taskflow, P&& pred, C&& callable)

runs a moved taskflow and keeps running it until the predicate becomes true and then invokes the callback

Parameters
taskflow | a moved tf::Taskflow
pred | a boolean predicate to return `true` for stop
callable | a callable object to be invoked after this run completes
Returns | a tf::Future that holds the result of the execution

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 else
    future.wait();

This member function is thread-safe.

###
template<typename T>
void tf::Executor::corun(T& target)

runs a target graph and waits until it completes using an internal worker of this executor

Template parameters
T | target type which has `tf::Graph& T::graph()` defined
Parameters
target | the target task graph object

The method runs a target graph which has `tf::Graph& T::graph()` defined and waits until the execution completes. Unlike the typical flow of calling `tf::Executor::run` series plus waiting on the result, this method must be called by an internal worker of this executor. The caller worker will participate in the work-stealing loop of the scheduler, therby avoiding potential deadlock caused by blocked waiting.

    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.

#### Attention

You must call tf::Executor::corun from a worker of the calling executor or an exception will be thrown.

###
template<typename P>
void tf::Executor::corun_until(P&& predicate)

keeps running the work-stealing loop until the predicate becomes true

Template parameters
P | predicate type
Parameters
predicate | a boolean predicate to indicate when to stop the loop

The method keeps the caller worker running in the work-stealing loop until the stop predicate becomes true.

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

#### Attention

You must call tf::Executor::corun_until from a worker of the calling executor or an exception will be thrown.

### void tf::Executor::wait_for_all()

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

### size_t tf::Executor::num_workers() const noexcept

queries the number of worker threads

Each worker represents one unique thread spawned by an executor upon its construction time.

    tf::Executor executor(4);
    std::cout << executor.num_workers();    // 4

### size_t tf::Executor::num_topologies() const

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)

### size_t tf::Executor::num_taskflows() const

queries the number of running taskflows with moved ownership

    executor.run(std::move(taskflow));
    std::cout << executor.num_taskflows();  // 0 or 1 (taskflow still running)

### int tf::Executor::this_worker_id() const

queries the id of the caller thread in 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 executor
    executor.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);

###
template<typename Observer, typename... ArgsT>
std::shared_ptr<Observer> tf::Executor::make_observer(ArgsT && ... args)

constructs an observer to inspect the activities of worker threads

Template parameters
Observer | observer type derived from tf::ObserverInterface
ArgsT | argument parameter pack
Parameters
args | arguments to forward to the constructor of the observer
Returns | a shared pointer to the created observer

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.

###
template<typename Observer>
void tf::Executor::remove_observer(std::shared_ptr<Observer> observer)

removes an observer from the executor

This member function is not thread-safe.

###
template<typename F>
auto tf::Executor::async(F&& func)

runs a given function asynchronously

Template parameters
F | callable type
Parameters
func | callable object
Returns | a std::future that will hold the result of the execution

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.

###
template<typename F>
auto tf::Executor::async(const std::string& name, F&& func)

runs a given function asynchronously and gives a name to this task

Template parameters
F | callable type
Parameters
name | name of the asynchronous task
func | callable object
Returns | a std::future that will hold the result of the execution

The method creates and assigns a name to an asynchronous task to run the given function, returning std::future object that eventually will hold the result Assigned task names will appear in the observers of the executor.

    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.

###
template<typename F>
void tf::Executor::silent_async(F&& func)

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

Template parameters
F | callable type
Parameters
func | callable object

This member function is more efficient than tf::Executor::async and is encouraged to use when you do not want 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.

###
template<typename F>
void tf::Executor::silent_async(const std::string& name, F&& func)

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

Template parameters
F | callable type
Parameters
name | assigned name to the task
func | callable object

This member function is more efficient than tf::Executor::async and is encouraged to use when you do not want a std::future to acquire the result or synchronize the execution. Assigned task names will appear in the observers of the executor.

    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.

###
template<typename F, typename... Tasks, std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr>
tf::AsyncTask tf::Executor::silent_dependent_async(F&& func, Tasks && ... tasks)

runs the given function asynchronously when the given dependents finish

Template parameters
F | callable type
Tasks | task types convertible to 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::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.

###
template<typename F, typename... Tasks, std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr>
tf::AsyncTask tf::Executor::silent_dependent_async(const std::string& name, F&& func, Tasks && ... tasks)

names and runs the given function asynchronously when the given dependents finish

Template parameters
F | callable type
Tasks | task types convertible to tf::AsyncTask
Parameters
name | assigned name to the task
func | callable object
tasks | asynchronous tasks on which this execution depends
Returns | a tf::AsyncTask handle

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.

###
template<typename F, typename I, std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr>
tf::AsyncTask tf::Executor::silent_dependent_async(F&& func, I first, I last)

runs the given function asynchronously when the given range of dependents 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::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.

###
template<typename F, typename I, std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr>
tf::AsyncTask tf::Executor::silent_dependent_async(const std::string& name, F&& func, I first, I last)

names and runs the given function asynchronously when the given range of dependents finish

Template parameters
F | callable type
I | iterator type
Parameters
name | assigned name to the task
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::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.

###
template<typename F, typename... Tasks, std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr>
auto tf::Executor::dependent_async(F&& func, Tasks && ... tasks)

runs the given function asynchronously when the given dependents finish

Template parameters
F | callable type
Tasks | task types convertible to 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.

    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 mixed the use of tf::AsyncTask handles returned by Executor::dependent_async and Executor::silent_dependent_async when specifying task dependencies.

This member function is thread-safe.

###
template<typename F, typename... Tasks, std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr>
auto tf::Executor::dependent_async(const std::string& name, F&& func, Tasks && ... tasks)

names and runs the given function asynchronously when the given dependents finish

Template parameters
F | callable type
Tasks | task types convertible to tf::AsyncTask
Parameters
name | assigned name to the task
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.

    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 mixed the use of tf::AsyncTask handles returned by Executor::dependent_async and Executor::silent_dependent_async when specifying task dependencies.

This member function is thread-safe.

###
template<typename F, typename I, std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr>
auto tf::Executor::dependent_async(F&& func, I first, I last)

runs the given function asynchronously when the given range of dependents 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.

    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 mixed the use of tf::AsyncTask handles returned by Executor::dependent_async and Executor::silent_dependent_async when specifying task dependencies.

This member function is thread-safe.

###
template<typename F, typename I, std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr>
auto tf::Executor::dependent_async(const std::string& name, F&& func, I first, I last)

names and runs the given function asynchronously when the given range of dependents finish

Template parameters
F | callable type
I | iterator type
Parameters
name | assigned name to the task
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.

    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 mixed the use of tf::AsyncTask handles returned by Executor::dependent_async and Executor::silent_dependent_async when specifying task dependencies.

This member function is thread-safe.

Tab / T to search, Esc to close
…

Search for symbols, directories, files, pages or modules. You can omit any prefix from the symbol or file path; adding a `:` or `/` suffix lists all members of given symbol or directory.

Use ↓ / ↑ to navigate through the list, Enter to go. Tab autocompletes common prefix, you can copy a link to the result using ⌘ L while ⌘ M produces a Markdown link.

Sorry, nothing was found.