CancellationToken.Register 在 CancellationTokenSource.Cancel() 被调用或 CancelAfter() 到期后触发,仅一次且仅当 token 未被释放;回调默认同步执行于取消线程,异常会被吞掉,需 try/catch;必须显式 Unregister 或 using 管理生命周期。
CancellationToken.Register 什么时候会触发?它只在 CancellationTokenSource.Cancel() 被调用(或 CancelAfter() 到期)后触发,且仅当该 CancellationToken 尚未被释放(比如 cts 已 Dispose())时才安全执行。不是“注册就立刻跑”,也不是“每次轮询都调”,而是纯事件式回调——一次取消,最多触发一次(除非重复注册多个委托)。
token 来自已 Dispose() 的 CancellationTokenSource,Register 不报错但回调永远不会执行cts.Cancel(),回调就在 UI 线程跑),可能阻塞主线程 —— 若需异步或切线程,得自己包装 Task.Run 或用 async + await 配合 TaskScheduler
CancellationToken.Register 的参数陷阱最常用的是 Register(Action) 重载,但它有隐藏行为:如果回调里抛异常,整个取消流程不会中断,但异常会被吞掉(.NET 默认不传播),你根本看不到错误 —— 这是线上排查“为什么清理没做”的高频盲区。
try/catch,尤其涉及文件关闭、数据库连接释放等操作state 参数的重载(Register(Action)更安全:可传入 IDisposable 实例,避免闭包捕获导致对象生命周期延长() => await DoCleanupAsync())—— Register 只接受同步委托,await 会被忽略,变成火种式执行(fire-and-forget),极易丢失异常和上下文返回值 CancellationTokenRegistration 是结构体,必须显式调用 Unregister() 才能解除绑定;否则即使 token 已失效,只要 cts 没被 GC,回调仍可能被调用(尤其在反复创建/取消的循环场景中)。
using 声明(因为 CancellationTokenRegistration 实现了 IDisposable):using var registration = token.Register(() => Console.WriteLine("cleanup"));registration.Unregister(),它返回 bool 表示是否成功(已触发则返回 false)CancellationTokenRegistration 不含终结器,不回收也不会泄露内存,但逻辑上容易“多清一次”或“漏清”ThrowIfCancellationRequested() 混用要注意什么?Register 和 ThrowIfCancellationRequested() 完全不同层:前者是“取消后干点啥”,后者是“我正干活,检查下要不要停”。它们可以共存,但顺序和时机很关键。
Task.Run 内部一边循环一边调 token.ThrowIfCancellationRequested(),又在外层 token.Register 注册了清理回调 —— 那么一旦触发取消,回调会在 OperationCanceledException 抛出**之后、任务彻底结束之前**执行token.ThrowIfCancellationRequested(),会直接抛二次异常(因为 token 已是取消态),导致未处理异常崩溃token.Register(() => { if (token.IsCancellationRequested) DoCleanup(); }) —— 多余,Register 触发本身就意味着已取消,不用再查Register,而是想清楚:这个清理动作,是不是必须在取消那一刻发生?有没有竞态?会不会被重复触发?要不要跨线程?这些细节不抠,上