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::
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();
If you do not need the return value or the future, you can use tf::
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
You can name an asynchronous task to facilitate profiling by using the methods tf::
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::
tf::Taskflow taskflow; tf::Executor executor; std::atomic<int> counter{0}; taskflow.emplace([&] (tf::Subflow& sf){ std::vector<tf::Future<void>> futures; 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();
If you do not need the return value or the future, you can use tf::
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();
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).
Similar to tf::
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(); });