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 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'; }
Understand the 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.