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

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

class to create a task handle over a taskflow node More...

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

## Public Member Functions
| Task ()=default
| constructs an empty task
| Task (const Task &other)
| constructs the task with the copy of the other task
Task & | operator= (const Task &other)
| replaces the contents with a copy of the other task
Task & | operator= (std::nullptr_t)
| replaces the contents with a null pointer
bool | operator== (const Task &rhs) const
| compares if two tasks are associated with the same taskflow node
bool | operator!= (const Task &rhs) const
| compares if two tasks are not associated with the same taskflow node
const std::string & | name () const
| queries the name of the task
size_t | num_successors () const
| queries the number of successors of the task
size_t | num_predecessors () const
| queries the number of predecessors of the task
size_t | num_strong_dependencies () const
| queries the number of strong dependencies of the task
size_t | num_weak_dependencies () const
| queries the number of weak dependencies of the task
Task & | name (const std::string &name)
| assigns a name to the task
template<typename C >
Task & | work (C &&callable)
| assigns a callable
template<GraphLike T>
Task & | composed_of (T &object)
| creates a module task from a taskflow
Task & | adopt (tf::Graph &&graph)
| creates a module task from a graph by taking over its ownership
template<typename... Ts>
Task & | precede (Ts &&... tasks)
| adds precedence links from this to other tasks
template<typename... Ts>
Task & | succeed (Ts &&... tasks)
| adds precedence links from other tasks to this
template<typename... Ts>
Task & | remove_predecessors (Ts &&... tasks)
| removes predecessor links from other tasks to this
template<typename... Ts>
Task & | remove_successors (Ts &&... tasks)
| removes successor links from this to other tasks
Task & | release (Semaphore &semaphore)
| makes the task release the given semaphore
template<typename I >
Task & | release (I first, I last)
| makes the task release the given range of semaphores
Task & | acquire (Semaphore &semaphore)
| makes the task acquire the given semaphore
template<typename I >
Task & | acquire (I first, I last)
| makes the task acquire the given range of semaphores
Task & | data (void *data)
| assigns pointer to user data
void | reset ()
| resets the task handle to null
void | reset_work ()
| resets the associated work to a placeholder
bool | empty () const
| queries if the task handle is associated with a taskflow node
bool | has_work () const
| queries if the task has a work assigned
template<typename V >
void | for_each_successor (V &&visitor) const
| applies an visitor callable to each successor of the task
template<typename V >
void | for_each_predecessor (V &&visitor) const
| applies an visitor callable to each predecessor of the task
template<typename V >
void | for_each_subflow_task (V &&visitor) const
| applies an visitor callable to each subflow task
size_t | hash_value () const
| obtains a hash value of the underlying node
TaskType | type () const
| returns the task type
void | dump (std::ostream &ostream) const
| dumps the task through an output stream
void * | data () const
| queries pointer to user data
std::exception_ptr | exception_ptr () const
| retrieves the exception pointer of this task
bool | has_exception_ptr () const
| queries if the task has an exception pointer

## Friends
class | FlowBuilder
class | Runtime
class | NonpreemptiveRuntime
class | Taskflow
class | TaskView
class | Executor

## Detailed Description

class to create a task handle over a taskflow node

A task points to a node in a taskflow graph and provides a set of methods for users to access and modify attributes of the associated node, such as dependencies, callable, names, and so on. A task is a very lightweight object (i.e., it only stores a node pointer) and can be trivially copied around.

// create two tasks with one dependency
auto task1 = taskflow.emplace([](){}).name("task1");
auto task2 = taskflow.emplace([](){}).name("task2");
task1.precede(task2);
// dump the task information through std::cout
task1.dump(std::cout);
tf::Task::name
const std::string & name() const
queries the name of the task
Definition task.hpp:1388

A task created from a taskflow can be one of the following types:

- tf::TaskType::STATIC - Static Tasking
- tf::TaskType::CONDITION - Conditional Tasking
- tf::TaskType::RUNTIME - Runtime Tasking
- tf::TaskType::SUBFLOW - Subflow Tasking
- tf::TaskType::MODULE - Composable Tasking

tf::Task task1 = taskflow.emplace([](){}).name("static task");
tf::Task task2 = taskflow.emplace([](){ return 3; }).name("condition task");
tf::Task task3 = taskflow.emplace([](tf::Runtime&){}).name("runtime task");
tf::Task task4 = taskflow.emplace([](tf::Subflow& sf){
tf::Task stask1 = sf.emplace([](){});
tf::Task stask2 = sf.emplace([](){});
}).name("subflow task");
tf::Task task5 = taskflow.composed_of(taskflow2).name("module task");
tf::FlowBuilder::emplace
Task emplace(C &&callable)
creates a static task
Definition flow_builder.hpp:1738
tf::Runtime
class to create a runtime task
Definition runtime.hpp:47
tf::Subflow
class to construct a subflow graph from the execution of a dynamic task
Definition flow_builder.hpp:1913
tf::Task
class to create a task handle over a taskflow node
Definition task.hpp:569
tf::Task::composed_of
Task & composed_of(T &object)
creates a module task from a taskflow
Definition task.hpp:1290

A tf::Task is polymorphic. Once created, you can assign a different task type to it using tf::Task::work. For example, the code below creates a static task and then reworks it to a subflow task:

tf::Task task = taskflow.emplace([](){}).name("static task");
task.work([](tf::Subflow& sf){
tf::Task stask1 = sf.emplace([](){});
tf::Task stask2 = sf.emplace([](){});
}).name("subflow task");
tf::Task::work
Task & work(C &&callable)
assigns a callable
Definition task.hpp:1491

Attention
  tf::Task does not own the lifetime of the associated node. Accessing the attributes of the associated node after the taskflow has been destroyed can result in undefined behavior.

## Constructor & Destructor Documentation

## ◆ Task() [1/2]

tf::Task::Task | ( |  | ) |
| default

constructs an empty task

An empty task is not associated with any node in a taskflow.

## ◆ Task() [2/2]

tf::Task::Task | ( | const Task & | other | ) |
| inline

constructs the task with the copy of the other task

Parameters

  other | the other task to copy

tf::Taskflow taskflow;
tf::Task A = taskflow.emplace([](){ std::cout << "Task A\n"; });
tf::Task B(A);
assert(B == A); // Now, B and A refer to the same underlying node
tf::Taskflow
class to create a taskflow object
Definition taskflow.hpp:64

## Member Function Documentation

## ◆ acquire() [1/2]

template<typename I >

Task & tf::Task::acquire | ( | I | first,
|  | I | last )

makes the task acquire the given range of semaphores

Note
  To know more about tf::Semaphore, please refer to Limit the Maximum Concurrency.

## ◆ acquire() [2/2]

Task & tf::Task::acquire | ( | Semaphore & | semaphore | ) |
| inline

makes the task acquire the given semaphore

Note
  To know more about tf::Semaphore, please refer to Limit the Maximum Concurrency.

## ◆ adopt()

Task & tf::Task::adopt | ( | tf::Graph && | graph | ) |
| inline

creates a module task from a graph by taking over its ownership

Parameters

  graph | the graph to adopt (moved into the task)

Returns
  a Task handle to the adopted module task

Unlike tf::Task::composed_of, which references an externally-owned tf::Taskflow, `adopt` transfers ownership of the given tf::Graph into the task. The graph's lifetime is managed by the executor once adopted, and the caller has no access to the moved-from graph afterward.

tf::Taskflow taskflow;
tf::Graph g;
tf::FlowBuilder{g}.emplace([]{ std::cout << "task in adopted graph\n"; });
taskflow.adopt(std::move(g)).name("adopted");
tf::FlowBuilder
class to build a task dependency graph
Definition flow_builder.hpp:114
tf::FlowBuilder::adopt
Task adopt(Graph &&graph)
creates a module task from a graph by taking over its ownership
Definition flow_builder.hpp:1795
tf::Graph
class to create a graph object
Definition graph.hpp:47

Note
  Please refer to Composable Tasking for details.

## ◆ composed_of()

template<GraphLike T>

Task & tf::Task::composed_of | ( | T & | object | ) |

creates a module task from a taskflow

Template Parameters

  T | type satisfying tf::GraphLike

Parameters

  object | a custom object that defines `T::graph()` method

Returns
  `*this`

The example below creates a module task from a taskflow:

task.composed_of(taskflow);

To understand how Taskflow schedules a module task including how to create a schedulable graph, pleas refer to Create a Custom Composable Graph.

## ◆ data() [1/2]

void * tf::Task::data | ( |  | ) | const
| inline

queries pointer to user data

Returns
  C-styled pointer to the attached user data by tf::Task::data(void* data)

The following example shows how to attach a user data to a task and retrieve it during the execution of the task.

tf::Executor executor;
tf::Taskflow taskflow("attach data to a task");
int data; // user data
// create a task and attach it a user data
auto A = taskflow.placeholder();
A.data(&data).work([A](){
auto d = *static_cast<int*>(A.data());
std::cout << "data is " << d << std::endl;
});
// run the taskflow iteratively with changing data
for(data = 0; data<10; data++){
executor.run(taskflow).wait();
}
tf::Executor
class to create an executor
Definition executor.hpp:62
tf::Executor::run
tf::Future< void > run(Taskflow &taskflow)
runs a taskflow once
tf::Task::data
void * data() const
queries pointer to user data
Definition task.hpp:1515

## ◆ data() [2/2]

Task & tf::Task::data | ( | void * | data | ) |
| inline

assigns pointer to user data

Parameters

  data | pointer to user data

Returns
  `*this`

The following example shows how to attach a user data to a task and retrieve it during the execution of the task.

tf::Executor executor;
tf::Taskflow taskflow("attach data to a task");
int data; // user data
// create a task and attach it a user data
auto A = taskflow.placeholder();
A.data(&data).work([A](){
auto d = *static_cast<int*>(A.data());
std::cout << "data is " << d << std::endl;
});
// run the taskflow iteratively with changing data
for(data = 0; data<10; data++){
executor.run(taskflow).wait();
}

## ◆ dump()

void tf::Task::dump | ( | std::ostream & | ostream | ) | const
| inline

dumps the task through an output stream

The method dumps the name and the type of this task through the given output stream.

task.dump(std::cout);

## ◆ empty()

bool tf::Task::empty | ( |  | ) | const
| inline

queries if the task handle is associated with a taskflow node

Returns
  `true` if the task is not associated with any taskflow node; otherwise `false`

tf::Task task;
assert(task.empty() == true);
tf::Task::empty
bool empty() const
queries if the task handle is associated with a taskflow node
Definition task.hpp:1413

Note that an empty task is not equal to a placeholder task. A placeholder task is created from tf::Taskflow::placeholder and is associated with a taskflow node, but its work is not assigned yet.

## ◆ exception_ptr()

std::exception_ptr tf::Task::exception_ptr | ( |  | ) | const
| inline

retrieves the exception pointer of this task

This method retrieves the exception pointer of this task that are silently caught by the executor, if any. When multiple tasks throw exceptions concurrently, only one exception will be propagated, while the others are silently caught and stored within their respective tasks. For example, in the code below, both tasks `B` and `C` throw exceptions. However, only one of them will be propagated to the try-catch block, while the other will be silently caught and stored within its respective task.

tf::Executor executor(2);
tf::Taskflow taskflow;
std::atomic<size_t> arrivals(0);
auto [B, C] = taskflow.emplace(
[&]() {
// wait for two threads to arrive so we avoid premature cancellation
++arrivals; while(arrivals != 2);
throw std::runtime_error("oops");
},
[&]() {
// wait for two threads to arrive so we avoid premature cancellation
++arrivals; while(arrivals != 2);
throw std::runtime_error("oops");
}
);
try {
executor.run(taskflow).get();
}
catch (const std::runtime_error& e) {
std::cerr << e.what();
}
// exactly one holds an exception as another was propagated to the try-catch block
assert((B.exception_ptr() != nullptr) != (C.exception_ptr() != nullptr));

## ◆ for_each_predecessor()

template<typename V >

void tf::Task::for_each_predecessor | ( | V && | visitor | ) | const

applies an visitor callable to each predecessor of the task

Template Parameters

  V | a callable type (function, lambda, etc.) that accepts a tf::Task handle

Parameters

  visitor | visitor to apply to each predecessor task

This method allows you to traverse and inspect predecessor tasks of this task. For instance, the code below iterates the two predecessors (`task2` and `task3`) of `task1`.

auto [task1, task2, task3] = taskflow.emplace(
[](){ std::cout << "task 1\n"; },
[](){ std::cout << "task 2\n"; },
[](){ std::cout << "task 3\n"; }
});
task1.succeed(task2, task3);
task1.for_each_predecessor([](tf::Task predecessor){
std::cout << "predecessor task " << predecessor.name() << '\n';
});

## ◆ for_each_subflow_task()

template<typename V >

void tf::Task::for_each_subflow_task | ( | V && | visitor | ) | const

applies an visitor callable to each subflow task

Template Parameters

  V | a callable type (function, lambda, etc.) that accepts a tf::Task handle

Parameters

  visitor | visitor to apply to each subflow task

This method allows you to traverse and inspect tasks within a subflow. It only applies to a subflow task.

tf::Task task = taskflow.emplace([](tf::Subflow& sf){
tf::Task stask1 = sf.emplace([](){}).name("stask1");
tf::Task stask2 = sf.emplace([](){}).name("stask2");
});
// Iterate tasks in the subflow and print each subflow task.
task.for_each_subflow_task([](tf::Task stask){
std::cout << "subflow task " << stask.name() << '\n';
});
tf::Task::for_each_subflow_task
void for_each_subflow_task(V &&visitor) const
applies an visitor callable to each subflow task
Definition task.hpp:1468

## ◆ for_each_successor()

template<typename V >

void tf::Task::for_each_successor | ( | V && | visitor | ) | const

applies an visitor callable to each successor of the task

Template Parameters

  V | a callable type (function, lambda, etc.) that accepts a tf::Task handle

Parameters

  visitor | visitor to apply to each subflow task

This method allows you to traverse and inspect successor tasks of this task. For instance, the code below iterates the two successors (`task2` and `task3`) of `task1`.

auto [task1, task2, task3] = taskflow.emplace(
[](){ std::cout << "task 1\n"; },
[](){ std::cout << "task 2\n"; },
[](){ std::cout << "task 3\n"; }
});
task1.precede(task2, task3);
task1.for_each_successor([](tf::Task successor){
std::cout << "successor task " << successor.name() << '\n';
});

## ◆ has_exception_ptr()

bool tf::Task::has_exception_ptr | ( |  | ) | const
| inline

queries if the task has an exception pointer

The method checks whether the task holds a pointer to a silently caught exception.

## ◆ has_work()

bool tf::Task::has_work | ( |  | ) | const
| inline

queries if the task has a work assigned

Returns
  `true` if the task has a work assigned (not placeholder); otherwise `false`

tf::Task task = taskflow.placeholder();
assert(task.has_work() == false);
// assign a static task callable to this task
task.work([](){});
assert(task.has_work() == true);
tf::Task::has_work
bool has_work() const
queries if the task has a work assigned
Definition task.hpp:1418

## ◆ hash_value()

size_t tf::Task::hash_value | ( |  | ) | const
| inline

obtains a hash value of the underlying node

Returns
  the hash value of the underlying node

The method returns std::hash on the underlying node pointer.

tf::Task task = taskflow.emplace([](){});
std::cout << "hash value of task is " << task.hash_value() << '\n';
tf::Task::hash_value
size_t hash_value() const
obtains a hash value of the underlying node
Definition task.hpp:1477

## ◆ name() [1/2]

const std::string & tf::Task::name | ( |  | ) | const
| inline

queries the name of the task

Returns
  the name of the task as a constant string reference

tf::Task task = taskflow.emplace([](){});
task.name("MyTask");
std::cout << "Task name: " << task.name() << std::endl;

## ◆ name() [2/2]

Task & tf::Task::name | ( | const std::string & | name | ) |
| inline

assigns a name to the task

Parameters

  name | a std::string

Returns
  `*this`

tf::Task task = taskflow.emplace([](){}).name("foo");
assert(task.name*) == "foo");

## ◆ num_predecessors()

size_t tf::Task::num_predecessors | ( |  | ) | const
| inline

queries the number of predecessors of the task

Returns
  the number of predecessor tasks

tf::Task A = taskflow.emplace([](){});
tf::Task B = taskflow.emplace([](){});
A.precede(B); // A is a predecessor of B
std::cout << "B has " << B.num_predecessors() << " predecessor(s)." << std::endl;
tf::Task::precede
Task & precede(Ts &&... tasks)
adds precedence links from this to other tasks
Definition task.hpp:1258
tf::Task::num_predecessors
size_t num_predecessors() const
queries the number of predecessors of the task
Definition task.hpp:1393

## ◆ num_strong_dependencies()

size_t tf::Task::num_strong_dependencies | ( |  | ) | const
| inline

queries the number of strong dependencies of the task

Returns
  the number of strong dependencies to this task

A strong dependency is a preceding link from one non-condition task to another task. For instance, task `cond` below has one strong dependency, while tasks `yes` and `no` each have one weak dependency.

auto [init, cond, yes, no] = taskflow.emplace(
[] () { },
[] () { return 0; },
[] () { std::cout << "yes\n"; },
[] () { std::cout << "no\n"; }
);
cond.succeed(init)
.precede(yes, no); // executes yes if cond returns 0
// executes no if cond returns 1

Note
  To understand how Taskflow schedule tasks under strong and weak dependencies, please refer to Conditional Tasking.

## ◆ num_successors()

size_t tf::Task::num_successors | ( |  | ) | const
| inline

queries the number of successors of the task

Returns
  the number of successor tasks.

tf::Task A = taskflow.emplace([](){});
tf::Task B = taskflow.emplace([](){});
A.precede(B); // B is a successor of A
std::cout << "A has " << A.num_successors() << " successor(s)." << std::endl;
tf::Task::num_successors
size_t num_successors() const
queries the number of successors of the task
Definition task.hpp:1408

## ◆ num_weak_dependencies()

size_t tf::Task::num_weak_dependencies | ( |  | ) | const
| inline

queries the number of weak dependencies of the task

Returns
  the number of weak dependencies to this task

A weak dependency is a preceding link from one condition task to another task. For instance, task `cond` below has one strong dependency, while tasks `yes` and `no` each have one weak dependency.

auto [init, cond, yes, no] = taskflow.emplace(
[] () { },
[] () { return 0; },
[] () { std::cout << "yes\n"; },
[] () { std::cout << "no\n"; }
);
cond.succeed(init)
.precede(yes, no); // executes yes if cond returns 0
// executes no if cond returns 1

Note
  To understand how Taskflow schedule tasks under strong and weak dependencies, please refer to Conditional Tasking.

## ◆ operator!=()

bool tf::Task::operator!= | ( | const Task & | rhs | ) | const
| inline

compares if two tasks are not associated with the same taskflow node

Parameters

  rhs | the other task to compare with

Returns
  true if they refer to different nodes; false otherwise

tf::Task A = taskflow.emplace([](){ std::cout << "A\n"; });
tf::Task B = taskflow.emplace([](){ std::cout << "B\n"; });
assert(A != B); // A and B refer to different nodes

## ◆ operator=() [1/2]

Task & tf::Task::operator= | ( | const Task & | other | ) |
| inline

replaces the contents with a copy of the other task

Parameters

  other | the other task to copy

tf::Task A = taskflow.emplace([](){ std::cout << "A\n"; });
tf::Task B;
B = A; // B now refers to the same node as A

## ◆ operator=() [2/2]

Task & tf::Task::operator= | ( | std::nullptr_t | ptr | ) |
| inline

replaces the contents with a null pointer

tf::Task A = taskflow.emplace([](){ std::cout << "A\n"; });
A = nullptr; // A no longer refers to any node

## ◆ operator==()

bool tf::Task::operator== | ( | const Task & | rhs | ) | const
| inline

compares if two tasks are associated with the same taskflow node

Parameters

  rhs | the other task to compare with

Returns
  true if both tasks refer to the same node; false otherwise

tf::Task A = taskflow.emplace([](){ std::cout << "A\n"; });
tf::Task B = A;
assert(A == B); // A and B refer to the same node

## ◆ precede()

template<typename... Ts>

Task & tf::Task::precede | ( | Ts &&... | tasks | ) |

adds precedence links from this to other tasks

Template Parameters

  Ts | parameter pack

Parameters

  tasks | one or multiple tasks

Returns
  `*this`

The example below creates a taskflow of two tasks, where `task1` runs before `task2`.

auto [task1, task2] = taskflow.emplace(
[](){ std::cout << "task1\n"; },
[](){ std::cout << "task2\n"; }
);
task1.precede(task2);

## ◆ release() [1/2]

template<typename I >

Task & tf::Task::release | ( | I | first,
|  | I | last )

makes the task release the given range of semaphores

Note
  To know more about tf::Semaphore, please refer to Limit the Maximum Concurrency.

## ◆ release() [2/2]

Task & tf::Task::release | ( | Semaphore & | semaphore | ) |
| inline

makes the task release the given semaphore

Note
  To know more about tf::Semaphore, please refer to Limit the Maximum Concurrency.

## ◆ remove_predecessors()

template<typename... Ts>

Task & tf::Task::remove_predecessors | ( | Ts &&... | tasks | ) |

removes predecessor links from other tasks to this

Template Parameters

  Ts | parameter pack

Parameters

  tasks | one or multiple tasks

Returns
  `*this`

This method removes the dependency links where the given tasks are predecessors of this task (i.e., tasks -> this). It ensures both sides of the dependency are updated to maintain graph consistency.

tf::Task A = taskflow.emplace([](){});
tf::Task B = taskflow.emplace([](){});
tf::Task C = taskflow.emplace([](){});
// create a linear chain of tasks, A->B->C
B.succeed(A)
.precede(C);
assert(B.num_successors() == 1 && C.num_predecessors() == 1);
// remove C from B's successor list
C.remove_predecessors(B);
assert(B.num_successors() == 0 && C.num_predecessors() == 0);
tf::Task::succeed
Task & succeed(Ts &&... tasks)
adds precedence links from other tasks to this
Definition task.hpp:1266
tf::Task::remove_predecessors
Task & remove_predecessors(Ts &&... tasks)
removes predecessor links from other tasks to this
Definition task.hpp:1274

## ◆ remove_successors()

template<typename... Ts>

Task & tf::Task::remove_successors | ( | Ts &&... | tasks | ) |

removes successor links from this to other tasks

Template Parameters

  Ts | parameter pack

Parameters

  tasks | one or multiple tasks

Returns
  `*this`

This method removes the dependency links where this task is a predecessor of the given tasks (i.e., this -> tasks). It ensures both sides of the dependency are updated to maintain graph consistency.

tf::Task A = taskflow.emplace([](){});
tf::Task B = taskflow.emplace([](){});
tf::Task C = taskflow.emplace([](){});
// create a linear chain of tasks, A->B->C
B.succeed(A)
.precede(C);
assert(B.num_successors() == 1 && C.num_predecessors() == 1);
// remove C from B's successor list
B.remove_successors(C);
assert(B.num_successors() == 0 && C.num_predecessors() == 0);
tf::Task::remove_successors
Task & remove_successors(Ts &&... tasks)
removes successor links from this to other tasks
Definition task.hpp:1282

## ◆ reset()

void tf::Task::reset | ( |  | ) |
| inline

resets the task handle to null

Resetting a task will remove its associated taskflow node and make it an empty task.

tf::Task task = taskflow.emplace([](){});
assert(task.empty() == false);
task.reset();
assert(task.empty() == true);
tf::Task::reset
void reset()
resets the task handle to null
Definition task.hpp:1378

## ◆ succeed()

template<typename... Ts>

Task & tf::Task::succeed | ( | Ts &&... | tasks | ) |

adds precedence links from other tasks to this

Template Parameters

  Ts | parameter pack

Parameters

  tasks | one or multiple tasks

Returns
  `*this`

The example below creates a taskflow of two tasks, where `task1` runs before `task2`.

auto [task1, task2] = taskflow.emplace(
[](){ std::cout << "task1\n"; },
[](){ std::cout << "task2\n"; }
);
task2.succeed(task1);

## ◆ type()

TaskType tf::Task::type | ( |  | ) | const
| inline

returns the task type

A task can be one of the types defined in tf::TaskType and can be printed in a human-readable form using tf::to_string.

auto task = taskflow.emplace([](){}).name("task");
std::cout << task.name() << " type=[" << tf::to_string(task.type()) << "]\n";
tf::to_string
const char * to_string(TaskType type)
convert a task type to a human-readable string
Definition task.hpp:66

## ◆ work()

template<typename C >

Task & tf::Task::work | ( | C && | callable | ) |

assigns a callable

Template Parameters

  C | callable type

Parameters

  callable | callable to construct a task

Returns
  `*this`

A tf::Task is polymorphic. Once created, you can reassign it to a different callable of a different task type using tf::Task::work. For example, the code below creates a static task and reworks it to a subflow task:

tf::Task task = taskflow.emplace([](){}).name("static task");
task.work([](tf::Subflow& sf){
tf::Task stask1 = sf.emplace([](){});
tf::Task stask2 = sf.emplace([](){});
}).name("subflow task");

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

- taskflow/core/task.hpp