在 ASP.NET Core 中注册可取消的后台服务需继承 BackgroundService 基类,重写 ExecuteAsync 并全程传递 CancellationToken;注册时调用 AddHostedService(),避免生命周期冲突,优先使用 PeriodicTimer 实现定时任务。
ASP.NET Core 的 IHostedService 是管理长时运行后台任务的标准方式,但原生不自动传递取消信号——必须显式接收 CancellationToken 并在关键阻塞点响应它。直接在 ExecuteAsync 中忽略 cancellationToken 参数,会导致应用关闭时任务强行终止,可能丢失数据或破坏状态。
正确做法是将传入的 CancellationToken 透传给所有支持它的异步 API(如 Task.Delay、HttpClient.GetAsync),并在非托管等待(如 Thread.Sleep)前手动检查 IsCancellationRequested。
AddHostedService() ,而非普通 AddSingleton
IServiceProvider 来解析服务——可能引发作用域生命周期冲突;改用 IServiceScopeFactory 按需创建作用域PeriodicTimer(.NET 6+)替代 Task.Delay 循环,它原生支持 CancellationToken
public class DataSyncService : IHostedService, IDisposable
{
private readonly IServiceScopeFactory _scopeFactory;
private Timer? _timer;
public DataSy
ncService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
return Task.CompletedTask;
}
private async void DoWork(object? state)
{
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService();
try
{
await dbContext.SyncDataAsync(cancellationToken); // 假设该方法接受 token
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
// 正常退出,不记录错误
}
catch (Exception ex)
{
// 记录未预期异常
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
_timer?.Dispose();
await Task.Delay(100, cancellationToken); // 给正在执行的 DoWork 留出收尾时间
}
public void Dispose() => _timer?.Dispose();
}
BackgroundService 是微软提供的抽象基类,它封装了启动/停止协调逻辑,并确保 StopAsync 被调用后,正在运行的 ExecuteAsync 任务能自然完成(除非超时)。裸写 IHostedService 容易漏掉对 cancellationToken 的传播,或在 StopAsync 中过早释放资源,导致 ObjectDisposedException。
BackgroundService 的 StopAsync 默认等待 ExecuteAsync 返回,且会把宿主的 cancellationToken 传入其中ExecuteAsync 内部有长时间无响应的同步操作(如文件锁、外部 API 同步调用),仍需自行添加超时和中断逻辑ExecuteAsync 中用 while (true) + await Task.Delay 无限循环——应改为 while (!stoppingToken.IsCancellationRequested)
即使用了 CancellationToken,后台任务仍可能无法及时响应取消,典型表现是应用关闭后进程卡住几秒甚至几十秒才退出。根本原因通常是某处阻塞操作没受 token 控制。
HttpClient 请求未传入 token:必须用 GetAsync(uri, cancellationToken),不能只用 GetAsync(uri)
ToListAsync(cancellationToken) 和 Dapper 的 QueryAsync(..., cancellationToken) 都需显式传参while (!token.IsCancellationRequested) { Thread.Sleep(100); } 应改为 await Task.Delay(100, token)
Task.Run(() => { ... }, cancellationToken) 中,并在内部定期轮询 token.IsCancellationRequested
本地调试时,Ctrl+C 或发送 SIGTERM 信号即可触发取消流程,但自动化测试需模拟宿主生命周期。不要直接 new 实例并调用 StartAsync——缺少 IHostApplicationLifetime 支持,StopAsync 不会被自动调用。
Host.CreateDefaultBuilder() 构建测试宿主,注入你的服务,再调用 host.StopAsync()
Task.Delay(100).Wait(cancellationToken) 模拟耗时操作,并验证是否在指定时间内完成BackgroundService 的默认超时是 5 秒(由 HostOptions.ShutdownTimeout 控制),测试时可临时缩短它以便快速验证真正难的不是加 cancellationToken,而是确认每一个 await 点、每一次 IO 调用、每一段同步等待都真正尊重了它——哪怕一个地方漏掉,整个取消链就断了。
# app
# ai
# 微软
# c#
# 作用域
# .net
# 为什么
# red
# while
# 封装
# 构造函数
# Token
# 循环
# 继承
# Thread
# 异步
# 数据库
# 自动化
# 并在
# 可取消
# 长时间
# 时间内
# 用了
# 自定义
# 重写
# 不支持
# 会把
相关栏目:
【
Google疑问12 】
【
Facebook疑问10 】
【
网络优化76771 】
【
技术知识130152 】
【
IDC云计算60162 】
【
营销推广131313 】
【
AI优化88182 】
【
百度推广37138 】
【
网站推荐60173 】
【
精选阅读31334 】
相关推荐:
php8.4匿名类怎么用_php8.4匿名类创建与使用场景【介绍】
win11 OneDrive怎么彻底关闭 Win11禁用并卸载OneDrive教程【分享】
Win11怎么查看显卡温度 Win11任务管理器查看GPU温度【技巧】
c++的STL算法库find怎么用 在容器中查找指定元素【实用教程】
Dapper的Execute方法的返回值是什么意思 Dapper Execute返回值详解
c++怎么使用std::unique实现去重_c++ 容器元素排序与连续重复删除【教程】
Win11怎么关闭自动更新 Win11永久关闭系统更新的有效方法【技巧】
Win11怎么设置ip地址_Windows 11手动配置网络IP教程【详解】
Win11怎么关闭专注助手 Win11关闭免打扰模式设置【操作】
LINUX怎么查看进程_LINUX ps命令查看运行服务
Win11怎么关闭边缘滑动手势_Windows11禁用触摸屏边缘操作
Windows10系统怎么查看CPU核心数_Win10逻辑处理器数量查看
Windows10系统怎么查看CPU温度_Win10性能监视器查看硬件数据
Python变量绑定机制_引用模型解析【教程】
c++如何使用std::bind绑定函数参数_c++ 占位符std::placeholders使用【详解】
如何使用Golang实现负载均衡_分发请求到多个服务节点
Win11怎么设置快速访问主页_Windows11资源管理器文件夹选项
Golang如何实现基本的用户注册_Golang用户注册表单处理示例
Windows10怎么用“讲述人”读屏辅助 Windows10轻松使用开启讲述人朗读屏幕文字帮助视障用户【教程】
Win11怎么更改计算机名_Windows11系统信息重命名设备教程
MAC如何启用访达侧边栏显示_MAC Finder偏好设置与常用目录添加【教程】
c++如何实现一个高性能的环形队列(Ring Buffer)_c++无锁实现方法【并发】
Win11怎么关闭系统推荐内容_Windows11开始菜单布局设置
Win11怎么设置虚拟键盘_打开Win11屏幕键盘操作指南【技巧】
php订单日志怎么记录物流_php记录订单物流变更日志指南【指南】
如何解决Windows时间不准的问题?(自动同步设置)
如何在 Go 中高效缓存与分发网络视频流
Go语言中CookieJar的持久化机制解析:内存存储与自定义持久化方案
Win11开机速度慢怎么优化_Win11系统启动加速设置指南【方法】
Windows10怎样连接蓝牙设备_Windows10蓝牙连接步骤【教程】
Linux怎么修改用户密码_Linux系统passwd命令使用与权限管理【方法】
如何使用Golang构建基础消息队列模拟_Golang消息发送与消费实现方法
MAC怎么在照片中添加水印_MAC自带编辑工具文字水印叠加【方法】
Go 中的 := 运算符:类型推导机制与使用边界详解
Win11怎么设置系统还原_Windows11系统属性保护设置
如何使用正则表达式批量替换重复的“-”模式为固定字符串
C++如何使用std::async进行异步编程?(future用法)
php修改数据怎么改富文本_update更新html内容注意事项【说明】
C++如何将C风格字符串(char*)转换为std::string?(代码示例)
Win11任务栏颜色怎么改_Win11自定义任务栏配色设置【美化】
Win11怎么设置任务栏对齐方式_Windows11个性化任务栏行为
Python解释执行模型_字节码流程说明【指导】
如何在JavaScript中动态拼接PHP的base_url与jQuery变量
Win11怎么设置开机自动连接宽带_Windows11创建拨号连接计划任务
Win10怎样清理C盘浏览器缓存_Win10清理浏览器缓存步骤【步骤】
如何在Golang中实现邮件发送功能_Golang SMTP发送与错误处理示例
Windows11如何设置专注助手_Windows11专注助手使用攻略【技巧】
Windows10如何更改任务栏高度_Win10解除锁定调整大小
Win11怎么关闭自动调节亮度_Windows11禁用内容自适应亮度
Win11怎么忘记WiFi网络_Win11删除已保存无线连接【教程】
2026-01-02
致胜网络推广营销网专注海外推广十年,是谷歌推广.Facebook广告全球合作伙伴,我们精英化的技术团队为企业提供谷歌海外推广+外贸网站建设+网站维护运营+Google SEO优化+社交营销为您提供一站式海外营销服务。