namespace tf {

/** @page AsyncTasking Asynchronous Tasking

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

@tableofcontents

@section LaunchAsynchronousTasksFromAnExecutor Launch Asynchronous Tasks from an Executor

%Taskflow executor provides an STL-styled method, 
tf::Executor::async,
for you to run a callable object asynchronously.
The method returns a @std_future that will eventually hold the result
of that function call.

@code{.cpp}
std::future<int> future = executor.async([](){ return 1; });
assert(future.get() == 1);
@endcode

@attention
Unlike std::async, the future object returned from tf::Executor::async does not block on destruction
until completing the function.

If you do not need the return value or use a future to synchronize the execution,
you are encouraged to use tf::Executor::silent_async which returns nothing and thus
has less overhead (i.e., no shared state management) compared to tf::Executor::async.

@code{.cpp}
executor.silent_async([](){
  // do some work without returning any result
});
@endcode

Launching asynchronous tasks from an executor is 
@em thread-safe and can be called by multiple threads both inside (i.e., worker) 
and outside the executor.
Our scheduler autonomously detects whether an asynchronous task is submitted 
from an external thread or a worker thread and schedules its execution
using work stealing.

@code{.cpp}
tf::Task my_task = taskflow.emplace([&](){
  // launch an asynchronous task from my_task
  executor.async([&](){
    // launch another asynchronous task that may be run by another worker
    executor.async([&](){});
  })
});
executor.run(taskflow);
executor.wait_for_all();   // wait for all tasks to finish
@endcode

@attention
Asynchronous tasks created from an executor does not belong to any taskflows.
The lifetime of an asynchronous task is managed automatically by the
executor that creates the task.

You can name an asynchronous task using the overloads, 
tf::Executor::async(const std::string& name, F&& f) and
tf::Executor::silent_async(const std::string& name, F&& f),
that take a string in the first argument.
Assigned names will appear in the observers of the executor.

@code{.cpp}
std::future<void> fu = executor.async("async task", [](){});
executor.silent_async("silent async task", [](){});
@endcode


@section LaunchAsynchronousTasksFromARuntime Launch Asynchronous Tasks from a Runtime

You can launch asynchronous tasks from tf::Runtime using
tf::Runtime::async or tf::Runtime::silent_async.
The following code creates 100 asynchronous tasks from a runtime
and joins their executions explicitly using tf::Runtime::corun_all.

@code{.cpp}
tf::Taskflow taskflow;
tf::Executor executor;

std::atomic<int> counter{0};

taskflow.emplace([&] (tf::Runtime& rt){
  for(int i=0; i<100; i++) {
    rt.silent_async([&](){ ++counter; }));
  }
  rt.corun_all();  // all of the 100 asynchronous tasks will finish by this join
  assert(counter == 100);
});
executor.run(taskflow).wait();
@endcode

Unlike tf::Subflow::join, you can call tf::Runtime::corun_all multiple times
to synchronize the execution of asynchronous tasks between different runs.
For example, the following code spawn 100 asynchronous tasks twice
and join each execution to assure the spawned 100 asynchronous tasks have
properly completed.

@code{.cpp}
tf::Taskflow taskflow;
tf::Executor executor;

std::atomic<int> counter{0};

taskflow.emplace([&] (tf::Runtime& rt){
  // spawn 100 asynchronous tasks and join
  for(int i=0; i<100; i++) {
    rt.silent_async([&](){ ++counter; }));
  }
  rt.corun_all();  // all of the 100 asynchronous tasks will finish by this join
  assert(counter == 100);
  
  // spawn another 100 asynchronous tasks and join
  for(int i=0; i<100; i++) {
    rt.silent_async([&](){ ++counter; }));
  }
  rt.corun_all();  // all of the 100 asynchronous tasks will finish by this join
  assert(counter == 200);
});
executor.run(taskflow).wait();
@endcode

By default, tf::Runtime does not join like tf::Subflow.
All pending asynchronous tasks spawned by tf::Runtime 
are no longer controllable when their parent runtime disappears.
It is your responsibility to properly synchronize spawned
asynchronous tasks using tf::Runtime::corun_all.

@attention
Creating asynchronous tasks from a runtime allows users to efficiently implement
parallel algorithms using recursion, such as parallel sort (tf::Taskflow::sort),
that demands dynamic parallelism at runtime.

*/

}


