如果没人倾听,你不会继续说话。那为什么用户离开后,你的代码还在运行?
我承认,曾经我也忽视CancellationToken。心想:"这有什么用?我的代码跑得好好的,何必复杂化?"后来才发现,这种想法正在慢慢蚕食我的应用性能。
不是以惊天崩溃的方式,甚至没有任何红色警报。只是些内存泄漏、不必要的CPU占用,以及服务器为早已离开的用户继续执行任务。
最糟糕的是——这本可以通过一个小小的改变来预防。
首先明确基础概念。CancellationToken是.NET中用于取消长时间运行操作的机制。它不会强制终止任何操作,只是礼貌地通知:"嘿,可以停下了,我们不再需要这个结果了。"
无论是处理Web API调用、后台服务还是密集循环,如果你不监听它,代码就会继续执行——即使早已没人等待结果。用户离开了,标签页关闭了,但你的代码?还在像忠实的仆人般继续工作。
曾经有个页面加载时会下载文件:
[HttpGet("download")]
public async Task<IActionResult> DownloadFile(string fileName)
{
var result = await DownloadFileAsync(fileName);
return Ok(result);
}
public async Task<string> DownloadFileAsync(string fileName)
{
await Task.Delay(5000); // 模拟长时间下载
return $"已下载 {fileName}";
}
起初这没什么问题,直到我发现:每次用户刷新页面或跳转时,新下载任务启动了,但旧任务呢?它依然在运行,占用内存,为空气用户卖力工作...
解决方法简单得令人尴尬:
[HttpGet("download")]
public async Task<IActionResult> DownloadFile(string fileName, CancellationToken token)
{
var result = await DownloadFileAsync(fileName, token);
return Ok(result);
}
public async Task<string> DownloadFileAsync(string fileName, CancellationToken token)
{
try
{
await Task.Delay(5000, token); // 尊重取消令牌
return $"已下载 {fileName}";
}
catch (OperationCanceledException)
{
return "下载已取消";
}
}
ASP.NET Core早已通过HttpContext.RequestAborted提供取消令牌。你只需要使用它。现在当用户刷新或关闭标签页时,服务器知道该停止工作。
没有幽灵任务,没有内存浪费,再也不会疑惑"为什么空闲应用占用800MB内存?"
多数开发者以为取消机制仅适用于异步代码——直到我在后台服务中发现这段代码:
public void ProcessBatch()
{
var items = Enumerable.Range(0, 1000).ToList();
foreach (var item in items)
{
// 模拟CPU密集型工作
ExpensiveComputation(item);
}
}
即使在优雅关闭期间,它依然...继续运行。解决方案:
public void ProcessBatch(CancellationToken token)
{
var items = Enumerable.Range(0, 1000).ToList();
foreach (var item in items)
{
if (token.IsCancellationRequested)
{
return; // 取消时提前退出
}
ExpensiveComputation(item);
}
}
现在它会定期检查取消请求,当系统发出信号时立即收拾行李离开。
有人说:"没关系,GC会清理的"
有人说:"我们的基础设施能处理额外任务"
确实——直到某天:
系统管理员都知道:小问题会积少成多。
不必过度执着,但需保持尊重。这是我的实践清单:
这是那种在酿成大祸前就会见效的好习惯。
本文只是冰山一角。
取消令牌背后还有完整体系:令牌组合、超时控制、链接令牌、后台工作者的优雅关闭、重试安全处理等。
你是否曾因忽略CancellationToken而踩坑?在评论区分享你的惊险故事,或者告诉我你想了解的高级模式——链接令牌?超时控制?还是重试逻辑?