Taskflow  3.2.0-Master-Branch
Loading...
Searching...
No Matches
Asynchronous Tasking

This chapters discusses how to launch tasks asynchronously so that you can incorporate independent, dynamic parallelism in your taskflows.

Launch Asynchronous Tasks from an Executor

Taskflow executor provides a STL-styled method, tf::Executor::async, for you to run a callable object asynchronously. The method returns a tf::Future object derived from std::future that will eventually hold the result of that function call. The result may be optional due to tf::Future::cancel (see Request Cancellation for details).

tf::Future<std::optional<int>> future = executor.async([](){ return 1; });
executor.wait_for_all();
assert(future.get() == 1);
tf::Future<void> future_of_void_return = executor.async([](){});
future_of_void_return.get();
class to access the result of an execution
Definition core/taskflow.hpp:571
Note
The future object returned from tf::Executor::async does not block on destruction.

If you do not need the return value or the future, you can use tf::Executor::silent_async which has less overhead of creating an asynchronous task compared to tf::Executor::async.

executor.silent_async([](){
// just do some stuff in the background ...
});

Launching asynchronous tasks from an executor is thread-safe and can be called from multiple threads or from the execution of a task. Our scheduler autonomously detects whether an asynchronous task is submitted from an external thread or a worker thread and schedules its execution in an efficient work-stealing loop.

tf::Task my_task = taskflow.emplace([&](){
// do some stuff
// ...
// launch an asynchronous task from my_task
executor.async([&](){
// do another asynchronous work
// ...
// launch another asynchronous task
executor.async([&](){});
})
});
executor.run(taskflow);
executor.wait_for_all(); // wait for all tasks to finish
class to create a task handle over a node in a taskflow graph
Definition task.hpp:187
Note
Asynchronous tasks created from an executor does not belong to any taskflows.

You can name an asynchronous task to facilitate profiling by using the methods tf::Executor::named_async and tf::Executor::named_silent_async.

tf::Future<void> future = executor.named_async("name of the task", [](){});
executor.silent_named_async("another name of the task", [](){});

Launch Asynchronous Tasks from a Subflow

You can launch asynchronous tasks from a subflow (tf::Subflow) using tf::Subflow::async. Asynchronous tasks created from a subflow are, and only, used with join (tf::Subflow::join) to describe independent tasks that are dynamically spawned during the execution of that subflow. When the subflow joins, all asynchronous tasks are guaranteed to finish. The following code creates 100 asynchronous tasks from a subflow, and these asynchronous tasks will complete by the time the subflow joins.

tf::Taskflow taskflow;
tf::Executor executor;
std::atomic<int> counter{0};
taskflow.emplace([&] (tf::Subflow& sf){
for(int i=0; i<100; i++) {
futures.emplace_back(sf.async([&](){ ++counter; }));
}
sf.join(); // all of the 100 asynchronous tasks will finish by this join
assert(counter == 100);
});
executor.run(taskflow).wait();
class to create an executor for running a taskflow graph
Definition executor.hpp:50
tf::Future< void > run(Taskflow &taskflow)
runs a taskflow once
Definition executor.hpp:1573
Task emplace(C &&callable)
creates a static task
Definition flow_builder.hpp:742
class to construct a subflow graph from the execution of a dynamic task
Definition flow_builder.hpp:889
void join()
enables the subflow to join its parent task
Definition executor.hpp:1826
auto async(F &&f, ArgsT &&... args)
runs a given function asynchronously
Definition executor.hpp:1908
class to create a taskflow object
Definition core/taskflow.hpp:73

If you do not need the return value or the future, you can use tf::Subflow::silent_async which has less overhead of creating an asynchronous task compared to tf::Subflow::async.

tf::Taskflow taskflow;
tf::Executor executor;
std::atomic<int> counter{0};
taskflow.emplace([&] (tf::Subflow& sf){
for(int i=0; i<100; i++) {
sf.silent_async([&](){ ++counter; });
}
sf.join(); // all of the 100 asynchronous tasks will finish by this join
assert(counter == 100);
});
executor.run(taskflow).wait();
void silent_async(F &&f, ArgsT &&... args)
similar to tf::Subflow::async but does not return a future object
Definition executor.hpp:1944

Creating asynchronous tasks from a subflow allows users to describe, for example, recursive algorithms that define only division without conquering or merging (e.g., parallel quick sort).

Attention
You should only create asynchronous tasks from a joined subflow. Launching asynchronous tasks from a detached subflow results in undefined behavior.

Similar to tf::Executor::named_async and tf::Executor::named_silent_async, you can name an asynchronous task in tf::Subflow to facilitate profiling by using the methods tf::Subflow::named_async and tf::Subflow::named_silent_async.

taskflow.emplace([](tf::Subflow& sf){
tf::Future<void> future = sf.named_async("name of the task", [](){});
sf.silent_named_async("another name of the task", [](){});
sf.join();
});
auto named_async(const std::string &name, F &&f, ArgsT &&... args)
runs the given function asynchronously and assigns the task a name
Definition executor.hpp:1854