取消与定时
Facade 为长期工作新增两项能力:任务协作取消(submit_cancellable + request_task_cancel)与定时句柄(TimerHandle / ScopedTimerHandle)。 两者都是"请求",不是"中断"。
可运行的完整演示见 examples/tutorial/13_cancellation_and_timers.cpp:
cpp
// 13_cancellation_and_timers.cpp
// 教程 13:协作取消与定时句柄。
//
// 演示:
// 1. submit_cancellable:executor 注入 StopToken,任务轮询协作退出;
// 2. request_task_cancel:排队取消(任务不开跑)与运行中协作请求;
// 3. TaskCancelled 异常与 CancellationStatus 独立计数(不是 failure);
// 4. submit_delayed_with_handle / TimerHandle:cancel 与 reschedule;
// 5. ScopedTimerHandle:RAII 析构即取消。
//
// 语义边界:
// - 取消是"请求"不是"中断":阻塞在无 wakeup 机制调用上的任务不会被强制打断;
// - deadline 仍是路由/诊断提示,不会自动触发取消;
// - TimerHandle 不绑定外部事件循环(asio strand 等),需要同一 strand
// 执行与销毁的定时工作继续由应用侧管理。
#include <atomic>
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <executor/executor.hpp>
using namespace executor;
using namespace std::chrono_literals;
namespace {
void demonstrate_queued_cancel(Executor& executor) {
std::cout << "\n[1] queued cancel: task never starts\n";
// 占住唯一 worker,保证下一个任务停留在队列中。
auto gate = std::make_shared<std::promise<void>>();
auto blocker = executor.submit_with_handle([gate]() noexcept {
gate->get_future().wait();
return 0;
});
std::atomic<bool> ran{false};
auto submission = executor.submit_with_handle([&ran]() noexcept {
ran.store(true, std::memory_order_release);
return 42;
});
const auto response = executor.request_task_cancel(submission.handle);
std::cout << " request_task_cancel -> "
<< to_string(response.result)
<< " (accepted=" << response.accepted() << ")\n";
gate->set_value(); // 释放 blocker,让队列流动。
(void)blocker.future.wait_for(5s);
try {
(void)submission.future.get();
std::cout << " unexpectedly ran\n";
} catch (const TaskCancelled& cancelled) {
std::cout << " future -> TaskCancelled(reason="
<< to_string(cancelled.reason()) << ")\n";
}
std::cout << " task ran: " << std::boolalpha << ran.load() << "\n";
}
void demonstrate_running_cooperative_cancel(Executor& executor) {
std::cout << "\n[2] running cancel: cooperative StopToken\n";
std::atomic<bool> started{false};
auto submission = executor.submit_cancellable([&started](StopToken token) noexcept {
started.store(true, std::memory_order_release);
int progress = 0;
while (!token.stop_requested() && progress < 100) {
std::this_thread::sleep_for(1ms);
++progress;
}
return progress; // 收到停止请求后正常返回,保留业务结果。
});
while (!started.load()) {
std::this_thread::yield();
}
const auto response = executor.request_task_cancel(submission.handle);
std::cout << " request_task_cancel -> " << to_string(response.result) << "\n";
const int progress = submission.future.get();
std::cout << " task stopped early at progress=" << progress << " (max 100)\n";
}
void demonstrate_cancellation_status(Executor& executor) {
std::cout << "\n[3] cancellation counters are lifecycle, not failures\n";
const CancellationStatus status = executor.get_cancellation_status();
std::cout << " request_count=" << status.request_count
<< " queued_cancelled=" << status.queued_cancelled_count
<< " running_request=" << status.running_request_count
<< " completed_after_request=" << status.completed_after_request_count
<< "\n";
const ExecutorFailureStatus failures = executor.get_failure_status();
std::cout << " failure total_count=" << failures.total_count
<< " (cancellations are not counted here)\n";
}
void demonstrate_timer_handle(Executor& executor) {
std::cout << "\n[4] TimerHandle: reschedule + cancel before expiry\n";
std::atomic<int> fired{0};
auto submission = executor.submit_delayed_with_handle(
500, [&fired]() noexcept { fired.fetch_add(1, std::memory_order_relaxed); });
std::cout << " reschedule_after(30) -> "
<< to_string(submission.handle.reschedule_after(30)) << "\n";
(void)submission.future.wait_for(10s);
auto status = submission.handle.status();
std::cout << " after fire: state="
<< (status ? to_string(status->state) : "unknown")
<< " execution_count=" << (status ? status->execution_count : 0u)
<< "\n";
auto doomed = executor.submit_delayed_with_handle(60'000, []() noexcept { return 1; });
std::cout << " cancel pending timer -> "
<< to_string(doomed.handle.cancel()) << "\n";
try {
(void)doomed.future.get();
} catch (const TaskCancelled& cancelled) {
std::cout << " pending future -> TaskCancelled(reason="
<< to_string(cancelled.reason()) << ")\n";
}
}
void demonstrate_scoped_timer(Executor& executor) {
std::cout << "\n[5] ScopedTimerHandle: destructor cancels\n";
std::atomic<bool> ran{false};
std::future<int> future;
{
auto submission = executor.submit_delayed_with_handle(60'000,
[&ran]() noexcept {
ran.store(true, std::memory_order_release);
return 1;
});
future = std::move(submission.future);
ScopedTimerHandle scoped(std::move(submission.handle));
std::cout << " scoped timer alive, leaving scope...\n";
} // ScopedTimerHandle 析构请求一次非阻塞取消。
(void)future.wait_for(5s);
std::cout << " after scope: ran=" << std::boolalpha << ran.load() << "\n";
}
} // namespace
int main() {
Executor executor;
ExecutorConfig config;
config.min_threads = 2;
config.max_threads = 4;
if (!executor.initialize(config)) {
std::cerr << "failed to initialize executor\n";
return 1;
}
demonstrate_queued_cancel(executor);
demonstrate_running_cooperative_cancel(executor);
demonstrate_cancellation_status(executor);
demonstrate_timer_handle(executor);
demonstrate_scoped_timer(executor);
std::cout << "\nshutdown: pending timers resolve with TaskCancelled(Shutdown)\n";
executor.shutdown();
std::cout << "tutorial 13 completed\n";
return 0;
}三种不同的承诺:排队超时、deadline、取消请求
这三个机制经常被混淆,但它们的承诺完全不同:
| 机制 | 谁触发 | 它做什么 | 它从不做什么 |
|---|---|---|---|
排队软超时(task_timeout_ms) | worker 开始执行任务前的时间流逝 | 跳过任务;future 收到 TimedOutException;计入超时诊断 | 打断已经开始运行的任务 |
TaskOptions::deadline | 仅为路由提示 | 只影响路由与诊断 | 自动触发取消或中断 |
显式取消(request_task_cancel) | 你的代码主动请求 | 排队中:任务不再执行,future 收到 TaskCancelled(Explicit)。运行中:置位任务的 StopToken | 抢占运行中的任务,或解除没有 wakeup 机制的阻塞调用 |
一句话总结:超时是线程池策略,deadline 是标签,取消是必须由任务配合响应的显式请求。
协作取消语义
submit_cancellable(f)把executor::StopToken注入为 callable 的首参数; 任务在工作步之间轮询token.stop_requested()。- 排队取消赢得唯一的仲裁点:任务不执行,future 以
TaskCancelled(Explicit)就绪,依赖它的任务收到TaskCancelled(DependencyCancelled),且不记录 failure 事件——取消是生命周期事件,由get_cancellation_status()独立计数。 - 运行中取消只置位 token。之后正常返回的任务保留业务结果;观察到请求后抛出
TaskCancelled的任务按取消归类。没有取消请求时主动抛TaskCancelled仍按任务失败统计,异常类型不能绕过 failure 体系。 - 重复/过期句柄幂等:运行中重复请求返回
AlreadyRequested,终态返回AlreadyCompleted,未知句柄返回NotFound,都不写 failure。
定时句柄
submit_delayed_with_handle() 与 submit_periodic_with_handle()(以及注入 StopToken 的 *_cancellable_* 变体)返回可复制的 TimerHandle:
- 到期前
cancel():返回CancelledBeforeDispatch,任务不执行,future 收到TaskCancelled(Explicit)。 - 派发后
cancel():返回CancellationRequestedAfterDispatch——取消继续向 排队/运行中的任务传播,而不是假装从未派发。 reschedule_after(ms)重排下一次到期(周期 timer 只改下一次触发时间、不改 周期);delay_ms <= 0返回InvalidDuration。- 析构不取消。需要"析构即取消"时用 move-only 的
ScopedTimerHandle包装。 - shutdown 时未到期的 delayed timer 以
TaskCancelled(Shutdown)就绪;计数见get_timer_status_summary()。
串行上下文派发
需要将 FIFO 串行工作纳入 Executor admission 时,可使用 SerialExecutionContext 与 submit_on(context, fn)。派发与结算分离:池 worker 只做有界非阻塞的 ticket 发布,业务 future 由串行线程直接结算,因此小型多 worker 池在突发提交下仍按 ticket FIFO 有界时间内前进,不会互相饥饿;排队取消、 超时与拒绝都会释放 ticket,不阻塞后续顺序。上下文关闭后拒绝新提交并排空已接收 任务;该 API 不绑定 asio strand,必须与外部 strand 同上下文销毁的对象仍由应用侧 管理。
不承诺的事
- 不抢占:阻塞在无 wakeup 机制的系统调用或库调用里的任务不会被中断,取消 不能强迫它停止。
- 不绑定 strand:facade 定时器把到期工作派发到普通线程池。回调与销毁必须在 同一外部事件循环 strand 上发生的 timer(例如 asio
steady_timer)继续由应用 侧管理——见与外部事件循环互操作。 - 不承诺定时精度:到期后派发到线程池,延迟取决于负载;用
benchmark_timer_precision实测,不要假设上界。
延伸阅读
- API 参考:
docs/API.md§3.8–3.9(定时与取消)。 - 仲裁内部机制与设计:
docs/design/task_cancellation_and_timers.md。 - 迁移指引(包括哪些自建定时可以迁移到
TimerHandle):docs/MIGRATION.md。