Asp.Net Core 托管服务

文章目录


前言

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

一、说明

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

二、使用步骤

1.创建托管服务

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

方式一:继承 BackgroundService

  1. MyBackgroundService.cs

    csharp 复制代码
    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

    csharp 复制代码
    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 将服务注册到依赖注入容器:
csharp 复制代码
builder.Services.AddHostedService<MyBackgroundService>();
// 或
builder.Services.AddHostedService<MyHostedService>();

3.处理作用域服务

  1. 若托管服务需要访问作用域服务(如 DbContext),需通过 IServiceScopeFactory 创建作用域:

    csharp 复制代码
    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

    csharp 复制代码
    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包

    csharp 复制代码
    Install-Package Quartz.Extensions.Hosting
  3. 示例:

    csharp 复制代码
    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 注册服务

    csharp 复制代码
    // 注册 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,结合依赖注入和异步编程模型,可以轻松构建可靠的后台任务。

相关推荐
ShadowYD5 小时前
Agent Loop:一个让 AI 编程 Agent 从"会写代码"进化到"闭环交付"的开源 Skill(适配国内外主流 Code Agent)
后端
考虑考虑6 小时前
nohup启动java程序
后端·自动化运维
aramae7 小时前
模拟实现strcmp()(C语言)
c语言·开发语言·后端
根目录下的猫9 小时前
RK3588适配的轻量级AI模型推荐
人工智能·后端·python·目标检测
星栈10 小时前
用 Rust 写 Agent 服务 -- adk-rust 上手记
后端·agent
杨运交11 小时前
[071][验证码模块]基于Spring拦截器的验证码认证设计思想
java·后端·spring
LucianaiB11 小时前
我用豆包工作 Seed-2.1-pro,复刻了 QQ 时代爆红的魔术图片
后端
码事漫谈11 小时前
智谱 ZCode 静默上传 Git 历史:48 小时信任危机复盘
后端
掘金码甲哥12 小时前
当对话模型遇上向量模型,vllm production stack 又该如何应对?
后端