尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

C# Web开发教程(十一)后台主动服务

C# Web开发教程(十一)后台主动服务
📅 发布时间:2026/6/19 7:28:21

托管服务(HostedService,也称为"后台服务")

  • 托管服务,这个翻译是不准确的,我觉得应该翻译成主动服务:服务器自己主动发起的服务(任务)[相对于客户端发起请求,服务端才响应]

    • 它是一种在应用启动后自动运行、无需外部触发的服务
  • 使用场景

- 代码运行在后台,比如服务器启动的时候在后台预先加载数据到缓存,每天凌晨3点把数据导出到备份数据库,或者每隔5秒钟在两张表之间同步一次数据。- 定时任务
  • 代码实现流程
- 实现IHostedService接口(用起来麻烦),一般编写从BackgroundService继承的类(用起来简单).
- 注册服务: services.AddHostedService<DemoBgService>();
  • 注意事项
- 一旦托管服务的代码有错,整个项目就无法启动- 可以把HostOptions.BackgroundServiceExceptionBehavior设置为Ignore,程序会忽略异常,而不是停止程序.- 不过不推荐这么搞,因为“异常应该被妥善的处理,而不是被忽略”.- 要在ExecuteAsync方法中把代码用try....catch包裹起来,当发生异常的时候,记录日志中或发警报等.- 而在.net6.0之前的版本中,托管代码就算有异常,项目也可以启动起来(旧的设计其实不好,所以新的版本修复了)
// HostedServiceDemo1.csnamespace WebApplicationAboutJWTConfigRun
{public class HostedServiceDemo1 : BackgroundService{protected override async Task ExecuteAsync(CancellationToken stoppingToken){Console.WriteLine("HostedService1启动");await Task.Delay(3000);string txt = await File.ReadAllTextAsync("d:/text.txt");Console.WriteLine("文件读取中...");await Task.Delay(10000);Console.WriteLine(txt);Console.WriteLine("HostedService1启动任务结束!");}}
}// Program.cs
......
builder.Services.AddSwaggerGen(c =>
{......
});
// 主动服务(注册)
builder.Services.AddHostedService<HostedServiceDemo1>();
  • 故意触发异常的效果
namespace WebApplicationAboutJWTConfigRun
{public class HostedServiceDemo1 : BackgroundService{protected override async Task ExecuteAsync(CancellationToken stoppingToken){try{Console.WriteLine("HostedService1启动");await Task.Delay(3000);string txt = await File.ReadAllTextAsync("d:/text.ext"); // 故意写错Console.WriteLine("文件读取中...");await Task.Delay(10000);Console.WriteLine(txt);Console.WriteLine("HostedService1启动任务结束!");}catch(Exception Ex){Console.WriteLine("启动代码异常" + Ex);}}}
}- 测试效果: 项目正常跑起来了,日志记录异常
......
启动代码异常System.IO.FileNotFoundException: Could not find file 'd:\text.ext'.
File name: 'd:\text.ext'
......
  • 托管服务中使用DI(依赖注入(DI)限制)
- 托管服务是以单例的生命周期注册到依赖注入容器中的。因此不能注入生命周期为范围或者瞬态的服务(不能直接注入 Scoped 或 Transient 服务(如 DbContext))。比如注入FCore的上下文的话,程序就会抛出异常。- 可以通过构造方法注入一个IServiceScopeFactory服务,它可以用来创建一个IServiceScope对象,这样我们就可以通过IServiceScope来创建短生命周期的服务了(记得在Dispose中释放IServiceScope)。
  • 异常实例演示
// HostedServiceDemo2.csnamespace WebApplicationAboutJWTConfigRun
{public class HostedServiceDemo2{public int Add(int a,int b){return a + b;}}
}// Program.cs
......
// 主动服务
builder.Services.AddHostedService<HostedServiceDemo1>();
// 注入生命周期为范围或者瞬态的服务
builder.Services.AddScoped<HostedServiceDemo2>();// HostedServiceDemo1.cs 测试
namespace WebApplicationAboutJWTConfigRun
{public class HostedServiceDemo1 : BackgroundService{private readonly HostedServiceDemo2 hostedServiceDemo2;// 依赖注入public HostedServiceDemo1(HostedServiceDemo2 hostedServiceDemo2){this.hostedServiceDemo2 = hostedServiceDemo2;}protected override async Task ExecuteAsync(CancellationToken stoppingToken){try{Console.WriteLine("HostedService1启动");// 执行逻辑,这里会触发异常Console.WriteLine("执行HostedService2服务"+hostedServiceDemo2.Add(1,1));......}catch(Exception Ex){Console.WriteLine("启动代码异常" + Ex);}}}
}
  • 现在,修复上面的异常
namespace WebApplicationAboutJWTConfigRun
{public class HostedServiceDemo1 : BackgroundService{//private readonly HostedServiceDemo2 hostedServiceDemo2;//public HostedServiceDemo1(HostedServiceDemo2 hostedServiceDemo2)//{//    this.hostedServiceDemo2 = hostedServiceDemo2;//}// 声明 IServiceScopeprivate IServiceScope serviceScope;// 依赖注入public HostedServiceDemo1(IServiceScopeFactory serviceScopeFactory){this.serviceScope = serviceScopeFactory.CreateScope();}protected override async Task ExecuteAsync(CancellationToken stoppingToken){try{var testService = serviceScope.ServiceProvider.GetRequiredService<HostedServiceDemo2>();Console.WriteLine("HostedService1启动");Console.WriteLine("执行HostedService2服务"+ testService.Add(1,1));await Task.Delay(3000);string txt = await File.ReadAllTextAsync("d:/text.txt");Console.WriteLine("文件读取中...");await Task.Delay(10000);Console.WriteLine(txt);Console.WriteLine("HostedService1启动任务结束!");}catch(Exception Ex){Console.WriteLine("启动代码异常" + Ex);}}public override void Dispose(){this.serviceScope.Dispose();base.Dispose();}}
}

✅ 总结要点

要点 说明
用途 后台任务、定时任务、数据同步等
实现方式 继承 BackgroundService
注册方式 AddHostedService()
异常处理 必须用 try-catch 包裹
DI 限制 不能直接注入 Scoped/Transient 服务
解决方案 使用 IServiceScopeFactory 创建作用域

连接数据库示例: 使用托管服务(BackgroundService)实现的定时数据导出任务

- 安装工具包<Project Sdk="Microsoft.NET.Sdk.Web">......<ItemGroup><PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" /><PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" /><PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.0" /><PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0" /><PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" /><PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0"><PrivateAssets>all</PrivateAssets><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets></PackageReference></ItemGroup></Project>// MyUser.csusing Microsoft.AspNetCore.Identity;namespace WebApplicationAboutJWTConfigRun
{public class MyUser:IdentityUser<long>{public string? WeiXinAccount { get; set; }}
}// MyRole.csusing Microsoft.AspNetCore.Identity;namespace WebApplicationAboutJWTConfigRun
{public class MyRole:IdentityRole<long>{}
}// MyDbContext.csusing Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;namespace WebApplicationAboutJWTConfigRun
{public class MyDbContext : IdentityDbContext<MyUser,MyRole,long>{public MyDbContext(DbContextOptions<MyDbContext> options) : base(options){}}
}// Program.cs
......
builder.Services.AddDbContext<MyDbContext>(opt =>
{opt.UseSqlServer("Server=.;Database=idtest2;Trusted_Connection=True;");
});- 作迁移和更新db// ScheduledService.csusing Microsoft.EntityFrameworkCore;namespace WebApplicationAboutJWTConfigRun
{public class ScheduledService : BackgroundService{// 解决单例服务无法直接使用Scoped服务的问题private readonly IServiceScope serviceScope;public ScheduledService(IServiceScopeFactory serviceScopeFactory){// 创建独立的作用域来获取DbContextthis.serviceScope = serviceScopeFactory.CreateScope();}protected override async Task ExecuteAsync(CancellationToken stoppingToken){try{var dbCtx = serviceScope.ServiceProvider.GetRequiredService<MyDbContext>();// 检查取消令牌:stoppingToken.IsCancellationRequested - 应用关闭时自动停止while (!stoppingToken.IsCancellationRequested){long c = await dbCtx.Users.LongCountAsync();await File.WriteAllTextAsync("d:/text.txt",c.ToString());await Task.Delay(5000);}Console.WriteLine("导出成功" + DateTime.Now);}catch (Exception ex){Console.WriteLine($"出错了: {ex.Message}, 堆栈跟踪: {ex.StackTrace}");}}public override void Dispose(){// 手动释放作用域:避免内存泄漏this.serviceScope.Dispose();// 调用基类Dispose:确保BackgroundService正确清理base.Dispose();}}
}// Program.cs
......
// 主动服务
builder.Services.AddHostedService<ScheduledService>();
builder.Services.AddDbContext<MyDbContext>(opt =>
{......  
}

相关新闻

  • 2025年11月全屋定制环保材料公司评测:从资质到服务的全面考察
  • 【程序算法题】洛谷, P1760 通天之汉诺塔, java实现。
  • 雷池 WAF 免费版深度体验:企业用 Lua 脚本拓展,护住跨境电商

最新新闻

  • 终极Spotify字体美化指南:3分钟打造你的专属音乐界面
  • 2026年6月18日每日60秒读懂世界
  • 终极指南:如何在本地部署Meta-Llama-3.1-8B-Instruct-GGUF大语言模型
  • AMD Nitro-E架构深度解析:3层高效扩散模型设计模式与资源优化策略
  • 深度解析LeVo架构:腾讯SongGeneration如何实现商业级AI音乐生成
  • JMeter核心元件深度解析:从原理到实战的性能测试设计指南

日新闻

  • 5分钟掌握Python进化算法:Geatpy高性能优化工具完全指南
  • Microchip 24AA044 EEPROM选型与应用全指南:从参数解析到实战编程
  • 华为的鸿蒙到底有多牛?为什么称作遥遥领先?

周新闻

  • 3步解锁iOS设备:applera1n激活锁绕过完全指南
  • 39 2026 人工智能证书终极盘点,普通人选 AI 证书可以从这些方向入手
  • Redis 暴露公网有多危险?从端口检查到补救步骤

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号