在 ASP.NET Core 中使用托管服务实现后台任务
在 ASP.NET Core 中,后台任务作为托管服务实现。 托管服务是一个类,具有实现 IHostedService 接口的后台任务逻辑。 本文提供了三个托管服务示例:
- 在计时器上运行的后台任务。
- 激活有作用域的服务的托管服务。 有作用域的服务可使用依赖项注入 (DI)。
- 按顺序运行的已排队后台任务。
程序包
基于辅助角色服务模板的应用使用 Microsoft.NET.Sdk.Worker
SDK,并且具有对 Microsoft.Extensions.Hosting 包的显式包引用。 有关示例,请参阅示例应用的项目文件 (BackgroundTasksSample.csproj
)。
对于使用 Microsoft.NET.Sdk.Web
SDK 的 Web 应用,通过共享框架隐式引用 Microsoft.Extensions.Hosting 包。 在应用的项目文件中不需要显式包引用。
IHostedService 接口(仅限于短期任务)
IHostedService 接口为主机托管的对象定义了两种方法:
StartAsync
StartAsync(CancellationToken) 包含用于启动后台任务的逻辑。 在以下操作之前调用 StartAsync
:
- 已配置应用的请求处理管道。
- 已启动服务器且已触发 IApplicationLifetime.ApplicationStarted。
StartAsync
应仅限于短期任务,因为托管服务是按顺序运行的,在 StartAsync
运行完成之前不会启动其他服务。
StopAsync
- StopAsync(CancellationToken) 在主机执行正常关闭时触发。
StopAsync
包含结束后台任务的逻辑。 实现 IDisposable 和终结器(析构函数)以处置任何非托管资源。
BackgroundService 基类(实现长时间运行)
BackgroundService 是用于实现长时间运行的 IHostedService 的基类。
调用 ExecuteAsync(CancellationToken) 来运行后台服务。 实现返回一个 Task,其表示后台服务的整个生存期。 在 ExecuteAsync 变为异步(例如通过调用 await
)之前,不会启动任何其他服务。 避免在 ExecuteAsync
中执行长时间的阻塞初始化工作。 StopAsync(CancellationToken) 中的主机块等待完成 ExecuteAsync
。
调用 IHostedService.StopAsync 时,将触发取消令牌。 当激发取消令牌以便正常关闭服务时,ExecuteAsync
的实现应立即完成。 否则,服务将在关闭超时后不正常关闭
定时执行的计数器后台任务
定时后台任务使用 System.Threading.Timer 类。 计时器触发任务的 DoWork
方法。 在 StopAsync
上禁用计时器,并在 Dispose
上处置服务容器时处置计时器:
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 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WorkerService1 { /// <summary> /// 计数器的后台任务 /// </summary> internal class TimedHostedService : IHostedService, IDisposable { private int executionCount = 0; private readonly ILogger<TimedHostedService> _logger; private Timer? _timer = null ; public TimedHostedService(ILogger<TimedHostedService> logger) { _logger = logger; } public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation( "开始: Timed Hosted Service running." ); //Timer 不等待先前的 DoWork 执行完成 //Timer 参数说明:1.回调方法,2.传递给回调方法的参数,3.多长时间开始执行回调,4.间隔多久执行一次回调 _timer = new Timer(DoWork, null , TimeSpan.Zero, TimeSpan.FromSeconds(3)); return Task.CompletedTask; } private void DoWork( object ? state) { //使用 Interlocked.Increment 以原子操作的形式将执行计数器递增,这可确保多个线程不会并行更新 executionCount var count = Interlocked.Increment( ref executionCount); _logger.LogInformation($ "计数工作: Timed Hosted Service is working. Count:{count} {DateTime.Now}" ); } public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation( "结束: Time Hosted Service is stopping." ); _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } public void Dispose() { _timer?.Dispose(); } } } |
已使用 AddHostedService
扩展方法在 IHostBuilder.ConfigureServices
(Program.cs
) 中注册该服务:
1 | builder.Services.AddHostedService<TimedHostedService>(); |
在后台任务中使用有作用域的服务
要在 BackgroundService 中使用有作用域的服务,请创建作用域。 默认情况下,不会为托管服务创建作用域。
作用域后台任务服务包含后台任务的逻辑。 如下示例中:
- 服务是异步的。
DoWork
方法返回Task
。 出于演示目的,在DoWork
方法中等待 10 秒的延迟。 - ILogger 注入到服务中。
将要执行的服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | internal interface IScopedProcessingService { Task DoWork(CancellationToken stoppingToken); } internal class ScopedProcessingService : IScopedProcessingService { private int executionCount = 0; private readonly ILogger<ScopedProcessingService> _logger; public ScopedProcessingService(ILogger<ScopedProcessingService> logger) { _logger = logger; } public async Task DoWork(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { executionCount++; _logger.LogInformation( "Scoped Processing Service is working. Count:{Count}" , executionCount); await Task.Delay(10000, stoppingToken); } } } |
后台任务
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 | internal class ConsumeScopedServiceHostedService : BackgroundService { private readonly ILogger<ConsumeScopedServiceHostedService> _logger; public IServiceProvider Services { get ; } public ConsumeScopedServiceHostedService(IServiceProvider service, ILogger<ConsumeScopedServiceHostedService> logger) { _logger = logger; this .Services = service; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation( "Consume Scoped Service Hosted Service runing." ); await DoWork(stoppingToken); } private async Task DoWork(CancellationToken stoppingToken) { _logger.LogInformation( "Consume Scoped Service Hosted Service is working." ); using ( var scope = Services.CreateScope()) { var scopedProcessingService = scope.ServiceProvider.GetRequiredService<IScopedProcessingService>(); await scopedProcessingService.DoWork(stoppingToken); } } public override Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation( "Consume Scoped Service Hosted Service is stopping." ); return base .StopAsync(cancellationToken); } } |
注册服务
1 2 | builder.Services.AddHostedService<ConsumeScopedServiceHostedService>(); builder.Services.AddScoped<IScopedProcessingService, ScopedProcessingService>(); |
异步定时后台任务
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 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WorkerService1 { internal class TimedHostedServiceAsync : BackgroundService { private readonly ILogger<TimedHostedServiceAsync> _logger; public TimedHostedServiceAsync(ILogger<TimedHostedServiceAsync> logger) { _logger = logger; } private int _executionCount; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation( "Timed Hosted Service running." ); //启动时先执行一次 await DoWork(); //下次执行时等待周期,即间隔4个小时间执行一次? using PeriodicTimer time = new PeriodicTimer(TimeSpan.FromSeconds(5)); try { while (await time.WaitForNextTickAsync(stoppingToken)) { await DoWork(); } } catch (OperationCanceledException) { _logger.LogInformation( "Timed Hosted Service is stopping." ); } } private async Task DoWork() { int count = Interlocked.Increment( ref _executionCount); _logger.LogInformation($ "我是5秒执行一次. Count:{count} {DateTime.Now}" ); await Task.Delay(1000); } } } |
注册服务
1 | builder.Services.AddHostedService<TimedHostedServiceAsync>(); |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析