Skip to content

取消与定时

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_msworker 开始执行任务前的时间流逝跳过任务;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 时,可使用 SerialExecutionContextsubmit_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