在AspNetCore中使用托管服务
是托管服务主要用于什么场景?
托管服务主要用于那些不便于运行在前台,不便于写在控制器中的代码。比如服务器启动的时候在后台预先加载数据到缓存,
每天凌晨3点把数据导出到备份数据库,每隔5秒在两张表之间同步一些数据,定时清理过期数据等等。
如何使用托管服务?
托管服务有两种实现方式:
- 创建一个类实现IHostedService接口,这个方法完全自定义,需要注意生命周期管理,然后还要实现两个方法
StartAsync(),StopAsync()最后在Program.cs中把服务注册到依赖注入容器。真心不建议使用这个
案例:使用IHostedService创建一个定时任务,每隔10秒执行一次,每次执行的任务是打印当前时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| namespace HostedService_AspNetCore01x02; public class TestHostedService : IHostedService { private readonly ILogger<TestHostedService> _logger; private Timer _timer; private int _executionCount; public TestHostedService(ILogger<TestHostedService> logger) { _logger = logger; } public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("定时托管服务启动."); _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(10)); return Task.CompletedTask; } private void DoWork(object state) { int count = Interlocked.Increment(ref _executionCount); _logger.LogInformation("定时托管服务运行 {Count}", count); } public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("定时托管服务已停止."); _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } }
|
在Program.cs中注册服务
1
| builder.Services.AddHostedService<TestHostedService>();
|
运行截图:

- 创建一个类,然后继承BackgroundService类,BackgroundService是官方提供的实现了IHostedService接口的抽象类。
然后去重写ExecuteAsync()方法,这个方法用于定义任务逻辑。这个方法同样需要在Program.cs中把服务注册到依赖注入容器。
使用BackgroundService的优势是它会自动管理生命周期,不需要自己去管理。推荐使用这个,问就是方便!!!
案例:使用BackgroundService创建一个定时任务,每隔10秒执行一次,每次执行的任务是打印当前时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| namespace HostedService_AspNetCore01x02; public class TestHostedService : BackgroundService { private readonly ILogger<TestHostedService> _logger; private int _executionCount;
public TestHostedService(ILogger<TestHostedService> logger) { _logger = logger; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("定时托管服务启动."); await DoWork(); using PeriodicTimer timer = new(TimeSpan.FromSeconds(10)); try { while (await timer.WaitForNextTickAsync(stoppingToken)) { await DoWork(); } } catch (OperationCanceledException) { _logger.LogInformation("定时托管服务已停止."); } }
private async Task DoWork() { int count = Interlocked.Increment(ref _executionCount); await Task.Delay(TimeSpan.FromSeconds(2)); _logger.LogInformation("定时托管服务运行 {Count}", count); } }
|
在Program.cs中注册服务
1
| builder.Services.AddHostedService<TestHostedService>();
|
运行截图:

这就是一个简单的定时任务,每隔10秒执行一次,每次执行的任务是打印当前时间,注意这个托管服务我没有注入Scoped服务。
注意事项
除了这些,在使用托管服务的时候需要注意生命周期的冲突。
我们都知道托管服务是Singleton服务,而DbContext是Scoped服务。
所以直接把DbContext依赖注入到托管服务中会报错。
因为DbContext本来是在一个作用域(如一次 HTTP 请求、一个手动 CreateScope)内创建,作用域结束时自动释放;
但托管服务是单例周期会一直存在,导致托管服务会一直持有这一个DbContext而不会释放,一直到进程退出,
这就是连接泄漏、内存泄漏的根源。
如何在托管服务中使用DbContext?
同时使用 using 和 IServiceScopeFactory:
- 使用IServiceScopeFactory提供“临时作用域”对象,让 Scoped 的 DbContext 能被正确解析出来。
- 使用using确保作用域结束时自动 Dispose(归还连接、清缓存)。
关键处代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private readonly IServiceScopeFactory _serviceScopeFactory;
public TestHostedService(IServiceScopeFactory serviceScopeFactory) { _serviceScopeFactory = serviceScopeFactory; }
private async Task DoWork() { using var scope = _serviceScopeFactory.CreateScope(); var dateContext = scope.ServiceProvider.GetRequiredService<DateContext>(); var toDoList = await dateContext.ToDoListTable .Include(t => t.TransactionNodes) .FirstOrDefaultAsync(x => x.Id == "1ca"); }
|
完整代码(这是一个一对多的事务列表及其事务节点的查询,这只是一个随便写的案例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| using Microsoft.EntityFrameworkCore;
namespace TestEFc_IEQable_AspNetCore01x02 { public class TestHostedService:BackgroundService { private readonly ILogger<TestHostedService> _logger; private readonly IServiceScopeFactory _serviceScopeFactory; private int _executionCount;
public TestHostedService(ILogger<TestHostedService> logger, IServiceScopeFactory serviceScopeFactory) { _logger = logger; _serviceScopeFactory = serviceScopeFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("定时托管服务启动."); await DoWork(); using PeriodicTimer timer = new(TimeSpan.FromSeconds(10)); try { while (await timer.WaitForNextTickAsync(stoppingToken)) { await DoWork(); } } catch (OperationCanceledException) { _logger.LogInformation("定时托管服务已停止."); } }
private async Task DoWork() { using var scope = _serviceScopeFactory.CreateScope(); var dateContext = scope.ServiceProvider.GetRequiredService<DateContext>(); var toDoList = await dateContext.ToDoListTable .Include(t => t.TransactionNodes) .FirstOrDefaultAsync(x => x.Id == "1ca"); _logger.LogInformation($"ToDoList Id:{toDoList.Id}, Title:{toDoList.ListTitle}, Description:{toDoList.ListDescription}, State:{toDoList.State}"); foreach (var item in toDoList.TransactionNodes) { _logger.LogInformation($"TransactionNode Id:{item.Id}, SerialNumber:{item.SerialNumber}, Content:{item.Content}, State:{item.State}"); } int count = Interlocked.Increment(ref _executionCount); await Task.Delay(TimeSpan.FromSeconds(2)); _logger.LogInformation("定时托管服务运行 {Count}", count); } } }
|
运行截图:
