System.Threading.Timer 是 Windows 服务或 ASP.NET Core 后台服务中启动轻量定时任务最稳妥的选择,适用于低频、快速、容错性高的场景,如每小时一次的临时文件清理;需用 try/catch 包裹回调、避免手动 timer.Change、组合 LastAccessTimeUtc 和 CreationTimeUtc 判断过期、按错误类型分级记录日志、在 IHostedService 中合理控制生命周期。

用 System.Threading.Timer 启动轻量定时任务最稳妥
Windows 服务或 ASP.NET Core 后台服务里,不需要引入 Hangfire 或 Quartz.NET 这类重型调度器——临时文件清理频率低(比如每小时一次)、执行快(毫秒级)、失败影响小,System.Threading.Timer 足够可靠,且不依赖外部存储或持久化。
它在后台线程池中运行,不会阻塞主线程;只要应用进程活着,它就一直有效。注意别用 System.Timers.Timer,它的 Elapsed 事件可能跨线程抛异常,且默认不捕获未处理异常,一出错就静默失效。
-
Timer构造时传入回调函数、初始延迟(如TimeSpan.Zero表示立即执行第一次)、周期间隔(如TimeSpan.FromHours(1)) - 回调函数内必须包
try/catch,否则未捕获异常会让整个 Timer 停摆 - 不要在回调里直接写
timer.Change(...)手动重置——周期模式下不需要,手动调会干扰节奏
清理前先判断文件是否「真正过期」而非只看创建时间
临时文件的“过期”逻辑不能只依赖 CreationTime。用户可能复制旧文件进来、系统时间被修改、或者文件被程序反复打开写入导致 LastWriteTime 刷新——单靠一个时间戳容易误删或漏删。
推荐组合判断:LastAccessTimeUtc(反映实际使用痕迹) + CreationTimeUtc(兜底防伪造),再加白名单过滤(比如跳过正在被占用的文件)。
- 用
File.GetLastAccessTimeUtc(path)比LastWriteTime更能反映“闲置多久”,尤其对缓存类文件 - 用
DateTime.UtcNow.AddHours(-2)做基准时间,比DateTime.Now避免本地时区/夏令时干扰 - 遇到
IOException或UnauthorizedAccessException直接跳过,别重试——说明文件正被其他进程锁定
删除失败时别吞异常,但要避免日志刷屏
清理过程出错是常态:文件被占用、权限不足、路径含非法字符、NTFS 硬链接搞乱了引用计数……全记 ERROR 级日志会导致日志爆炸,掩盖真正问题。
按错误类型分级处理更实用:
-
IOException和UnauthorizedAccessException→ 记 WARN,附上文件路径和ex.Message -
DirectoryNotFoundException或PathTooLongException→ 记 INFO,属于预期边缘情况 - 其他异常(如
NullReferenceException)→ 记 ERROR,说明代码有 bug - 每次清理后记录共扫描几个文件、成功删几个、跳过几个,方便后续核对
ASP.NET Core 里注册为 IHostedService 要注意生命周期
如果项目是 Web 应用,把清理逻辑塞进 IHostedService 是标准做法,但容易踩两个坑:启动时没等应用完全就绪、关闭时强行中断还在跑的删除任务。
StopAsync 不该直接 timer.Dispose(),而应设标志位让下一轮回调自然退出;StartAsync 里也不该立刻开始清理,最好延迟几秒等中间件加载完。
- 用
CancellationTokenSource配合Task.Delay(5000, stoppingToken)实现启动延迟 - 在
Timer回调开头检查cancellationToken.IsCancellationRequested,是则直接 return -
StopAsync中调用cancellationTokenSource.Cancel(),然后await Task.WhenAny(timerTask, Task.Delay(3000))给最多 3 秒收尾时间 - 别在
Dispose方法里做清理——.NET 不保证它一定被调用
临时文件路径往往分散在多个位置(Path.GetTempPath()、AppContext.BaseDirectory + "cache"、甚至用户自定义配置),清理逻辑得支持多目录扫描。别假设所有文件都归你管,也别指望一次清理就彻底干净——重点是稳定、可观察、不伤主线业务。


















