Request Cancellation
Contents
This chapters discusses how to cancel submitted tasks.
Cancel Execution of Taskflows
When you submit a taskflow to an executor (e.g., tf::
tf::Executor executor; tf::Taskflow taskflow; for(int i=0; i<1000; i++) { taskflow.emplace([](){ std::this_thread::sleep_for(std::chrono::seconds(1)); }); } // submit the taskflow tf::Future fu = executor.run(taskflow); // request to cancel the above submitted execution fu.cancel(); // wait until the cancellation completes fu.get();
When you request a cancellation, the executor will stop scheduling the rest tasks of the taskflow. Tasks that are already running at the time of requesting cancellation will continue to finish, but their successor tasks will not be scheduled to run. A cancellation is considered complete when all these running tasks finish. To wait for a cancellation to complete, you may explicitly call tf::Future::get
.
For instance, the following code results in undefined behavior:
tf::Executor executor; { tf::Taskflow taskflow; for(int i=0; i<1000; i++) { taskflow.emplace([](){}); } tf::Future fu = executor.run(taskflow); fu.cancel(); // there can still be task running after cancellation } // destroying taskflow here can result in undefined behavior
The undefined behavior problem exists because tf::get
to ensure the cancellation completes before the end of the scope destroys the taskflow.
tf::Executor executor; { tf::Taskflow taskflow; for(int i=0; i<1000; i++) { taskflow.emplace([](){}); } tf::Future fu = executor.run(taskflow); fu.cancel(); // there can still be task running after cancellation fu.get(); // waits until the cancellation completes }
Cancel Execution of Asynchronous Tasks
You can cancel submitted asynchronous tasks using tf::
tf::Executor executor; std::vector<tf::Future<void>> futures; // submit 10000 asynchronous tasks for(int i=0; i<10000; i++) { futures.push_back(executor.async([i](){ printf("task %d\n", i); std::this_thread::sleep_for(std::chrono::milliseconds(100)); })); } // cancel all asynchronous tasks for(auto& fu : futures) { fu.cancel(); } // wait until the 10000 cancellations complete executor.wait_for_all();
task 0 task 5 task 7 task 9 task 4 task 1 task 6 task 8 task 2 task 10 task 3 task 11
Similar to cancelling a running taskflow, cancelling a submitted asynchronous task does not guarantee the execution will be cancelled. The result depends on the present available workers and whether the asynchronous task is being run by a worker. To wait for a cancellation to complete, you may explicitly call tf::Future::get
. The result may be a std::
tf::Executor executor; tf::Future<std::optional<int>> fu = executor.async([](){ return 1; }; fu.cancel(); // call tf::Future::get to wait for the cancellation to complete if(auto ret = fu.get(); ret == std::nullopt) { std::cout << "asynchronous task 1 is cancelled\n"; } else { std::cout << "asynchronous task 1 returns " << ret.value() << '\n'; }
Limitations of Cancellation
Canceling the execution of a running taskflow has the following limitations:
- Cancellation is non-preemptive. A running task will not be cancelled until it finishes.
- Cancelling a taskflow with tasks acquiring and/or releasing tf::
Semaphore results is currently not supported.
We may overcome these limitations in the future releases.