Asp.Net Core 托管服务

发布于:2025-05-30 ⋅ 阅读:(21) ⋅ 点赞:(0)


前言

ASP.NET Core中,托管服务(Hosted Services)允许你在后台运行长时间运行的任务,例如定时任务、消息队列处理、数据清理等。

一、说明

托管服务需要实现IHostedService接口,或者继承BackgroundService类。BackgroundServiceASP.NET Core提供的抽象类,可能更方便,因为它已经实现了部分接口方法。

二、使用步骤

1.创建托管服务

托管服务需实现 IHostedService 接口或继承 BackgroundService 类(推荐后者,因为它简化了实现)。

方式一:继承 BackgroundService

  1. MyBackgroundService.cs
    using HostedService.Data;
    using Microsoft.EntityFrameworkCore;
    
    namespace HostedService.Service
    {
        public class MyBackgroundService : BackgroundService
        {
            private readonly ILogger<MyBackgroundService> _logger;
            public MyBackgroundService(ILogger<MyBackgroundService> logger, IServiceScopeFactory scopeFactory)
            {
                _logger = logger;
            }
            protected override async Task ExecuteAsync(CancellationToken stoppingToken)
            {
                _logger.LogInformation("后台服务启动成功");
                while (!stoppingToken.IsCancellationRequested)
                {
                    _logger.LogInformation("开始导出数据。。。");
                    //处理数据导出
                    _logger.LogInformation("导出数据成功。。。");
                }
            }
        }
    }
    

方式二:直接实现 IHostedService

  1. MyHostedService.cs
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    using System.Threading;
    using System.Threading.Tasks;
    
    public class MyHostedService : IHostedService
    {
        private readonly ILogger<MyHostedService> _logger;
        private Timer _timer;
    
        public MyHostedService(ILogger<MyHostedService> logger)
        {
            _logger = logger;
        }
    
        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("托管服务已启动。");
            _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
            return Task.CompletedTask;
        }
    
        private void DoWork(object state)
        {
            _logger.LogInformation("执行定时任务...");
        }
    
        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("托管服务已停止。");
            _timer?.Dispose();
            return Task.CompletedTask;
        }
    }
    

2.注册托管服务

  1. 在 Program.cs 中,通过 AddHostedService 将服务注册到依赖注入容器:
builder.Services.AddHostedService<MyBackgroundService>();
// 或
builder.Services.AddHostedService<MyHostedService>();

3.处理作用域服务

  1. 若托管服务需要访问作用域服务(如 DbContext),需通过 IServiceScopeFactory 创建作用域:
    
    using HostedService.Data;
    using Microsoft.EntityFrameworkCore;
    
    namespace HostedService.Service
    {
        public class MyBackgroundService : BackgroundService
        {
            private readonly ILogger<MyBackgroundService> _logger;
            private readonly IServiceScope _scope;
            public MyBackgroundService(ILogger<MyBackgroundService> logger, IServiceScopeFactory scopeFactory)
            {
                _logger = logger;
                _scope = scopeFactory.CreateScope();
            }
            public override void Dispose()
            {
                this._scope.Dispose();
                base.Dispose();
            }
            protected override async Task ExecuteAsync(CancellationToken stoppingToken)
            {
                var db=_scope.ServiceProvider.GetRequiredService<MyDbContext>();
                _logger.LogInformation("后台服务启动成功");
                while (!stoppingToken.IsCancellationRequested)
                {
                    _logger.LogInformation("开始导出数据。。。");
                    //使用db操作数据库并导出
                    long count=await db.Users.CountAsync();
                    await File.WriteAllTextAsync("d:/Hosted.txt",count.ToString());
                    await Task.Delay(5000);
                    _logger.LogInformation("导出数据成功。。。");
                }
    
            }
        }
    }
    

4.使用定时器(System.Threading.Timer)

  1. 托管服务通常结合定时器实现周期性任务:使用 BackgroundService + PeriodicTimer
    
    using HostedService.Data;
    using Microsoft.EntityFrameworkCore;
    
    namespace HostedService.Service
    {
        public class MyBackgroundService : BackgroundService
        {
            private readonly ILogger<MyBackgroundService> _logger;
            private readonly IServiceScope _scope;
            private PeriodicTimer? _timer;
            public MyBackgroundService(ILogger<MyBackgroundService> logger, IServiceScopeFactory scopeFactory)
            {
                _logger = logger;
                _scope = scopeFactory.CreateScope();
            }
            
            protected override async Task ExecuteAsync(CancellationToken stoppingToken)
            {
                _timer = new PeriodicTimer(TimeSpan.FromSeconds(5)); // 每5秒执行一次                                                                 
                var db = _scope.ServiceProvider.GetRequiredService<MyDbContext>();
                while (await _timer.WaitForNextTickAsync(stoppingToken))
                {
                    try
                    {
                        // 执行任务
                        await DoWorkAsync(db);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "定时任务执行失败");
                    }
                    
                }
    
            }
            private async Task DoWorkAsync(MyDbContext db)
            {
                _logger.LogInformation("后台服务启动成功");            
                _logger.LogInformation("开始导出数据。。。");
                long count = await db.Users.CountAsync();
                await File.WriteAllTextAsync("d:/Hosted.txt", count.ToString());
                await Task.Delay(5000);
                _logger.LogInformation("导出数据成功。。。");
                await Task.CompletedTask;
            }
            public override void Dispose()
            {
                this._scope.Dispose();
                base.Dispose();
            }
            public override async Task StopAsync(CancellationToken stoppingToken)
            {
                _timer?.Dispose();
                await base.StopAsync(stoppingToken);
            }
        }
    }
    

5.结合 Quartz.NET 实现复杂调度

  1. 对于复杂的定时任务,可集成第三方库(如 Quartz.NET
  2. 安装必要的Nuget包
    Install-Package Quartz.Extensions.Hosting
    
  3. 示例:
    using HostedService.Data;
    using Microsoft.EntityFrameworkCore;
    using Quartz;
    using static Quartz.Logging.OperationName;
    
    namespace HostedService.Service
    {
        public class QuartzHostedService : IHostedService
        {
            private readonly ISchedulerFactory _schedulerFactory;
            
            public QuartzHostedService(ISchedulerFactory schedulerFactory)
            {
                _schedulerFactory = schedulerFactory;
            }
    
            private IScheduler _scheduler;
            public async Task StartAsync(CancellationToken cancellationToken)
            {
                _scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
                var job = JobBuilder.Create<MyJob>().Build();
                var trigger = TriggerBuilder.Create()
                    .StartNow()
                    .WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever())
                    .Build();
                await _scheduler.ScheduleJob(job, trigger, cancellationToken);
                await _scheduler.Start(cancellationToken);
            }
    
            public Task StopAsync(CancellationToken cancellationToken)
            {
                throw new NotImplementedException();
            }
        }
        public class MyJob : IJob
        {
            private readonly IServiceScope _scope;
            private readonly ILogger<MyJob> _logger;
    
            public MyJob(ILogger<MyJob> logger, IServiceScopeFactory scopeFactory)
            {
                _logger = logger;
                _scope = scopeFactory.CreateScope();
            }
    
            public async Task Execute(IJobExecutionContext context)
            {
                var db = _scope.ServiceProvider.GetRequiredService<MyDbContext>();
                _logger.LogInformation("后台服务启动成功");
                _logger.LogInformation("开始导出数据。。。");
                long count = await db.Users.CountAsync();
                await File.WriteAllTextAsync("d:/Hosted.txt", count.ToString());
                await Task.Delay(5000);
                _logger.LogInformation("导出数据成功。。。");
                await Task.CompletedTask;
                Console.WriteLine("Quartz 任务执行中...");
                await Task.CompletedTask;
            }
        }
    }
    
  4. Program.cs中添加Quartz注册服务
    // 注册 Quartz
    builder.Services.AddQuartz(q =>
    {
        // 添加作业和触发器
        var jobKey = new JobKey("MyJob");
        q.AddJob<MyJob>(opts => opts.WithIdentity(jobKey));
        q.AddTrigger(opts => opts
            .ForJob(jobKey)
            .WithCronSchedule("0/10 * * * * ?")
        );
    });
    
    // 添加托管服务
    builder.Services.AddQuartzHostedService();
    

三、. 注意事项

  • 生命周期:托管服务默认是单例的,避免在构造函数中注入作用域服务。
  • 优雅关闭:正确处理 CancellationToken,确保任务能及时停止。
  • 异步操作:避免在 ExecuteAsync 中使用阻塞操作,优先使用异步方法。
  • 异常处理:捕获并记录异常,防止服务因未处理异常而终止。

总结

  • 托管服务是ASP.NET Core中实现后台任务的推荐方式,适用于:

    • 定时任务(如数据同步、缓存刷新)
    • 消息队列消费
    • 资源监控
    • 其他需要长时间运行的进程

    通过 BackgroundServiceIHostedService,结合依赖注入和异步编程模型,可以轻松构建可靠的后台任务。


网站公告

今日签到

点亮在社区的每一天
去签到