ASP.NET Core 通过 IHostedService 实现后台任务运行

目标是让网站在后台每月进行一次用户统计,考虑过直接在数据库操作,但是最后还是选择直接在程序内实现。

虽然功能是实现了,但是看过文档和其他解释后还没能理解,所以这里只写How不写Why。

环境

  • dotnet core 2.1

参考

步骤

官方一共写了三种后台任务用例,分别为Timed background tasks(定时任务)、scoped service background tasks(有作用域服务) 和 Queued background tasks(队列任务)。

对于定时向数据库写入内容,我的实现方式是 timed bg task + scoped service 结合,即在定时任务内执行有作用域的服务(数据库操作)。一开始尝试只用定时方式,但是数据库的上下文依赖注入一直失败,之后在StackOverflow上找到了需要实现一样功能的人,发现他的写法结合了两种方式,最后添加了scoped service方式后成功写入数据库。

  1. 首先实现scoped service,在类中添加接口,以及实现接口的类,在类中写入数据库相关代码
    public class DbOperation {
        internal interface IScopedProcessingService {
            void DoWork();
        }
    
        internal class ScopedProcessingService : IScopedProcessingService {
            private readonly TestDbContext db;
    
            public ScopedProcessingService(TestDbContext _db) {
                db = _db;
            }
    
            ... ...
    
            public void DoWork() {
                var user = new User() {
                    name = GetRandomName(),
                    age = GetRandomAge()
                };
    
                db.Users.Add(user);
                db.SaveChanges();
            }
        }
    }

    DbOperation中定义了接口IScopedProcessingService和接口实现ScopedProcessingServiceScopedProcessingService中实现了接口中声明的,向数据库写入新条目的方法DoWork()

  2. 新建定时任务类,并在定时执行的方法中调用数据库操作服务
    internal class WriteToDb : IHostedService, IDisposable {
        private readonly ILogger logger;
        private Timer timer;
    
        public WriteToDb(ILogger<WriteToDb> _logger, IServiceProvider services) {
            Services = services;
            logger = _logger;
        }
    
        public IServiceProvider Services { get; }
    
        public Task StartAsync(CancellationToken cancellationToken) {
            logger.LogInformation("Starting");
    
            timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromHours(1)); //新建定时任务
    
            return Task.CompletedTask;
        }
    
        public void DoWork(object state) { //数据库服务
            using (var scope = Services.CreateScope()) {
                var scopedProcessingService =
                    scope.ServiceProvider.GetRequiredService<DbOperation.IScopedProcessingService>();
    
                scopedProcessingService.DoWork(); //调用写入新条目的方法
            }
            logger.LogInformation("Insert New User");
        }
    
        public Task StopAsync(CancellationToken cancellationToken) {
            logger.LogInformation("Stopping");
    
            timer?.Change(Timeout.Infinite, 0);
    
            return Task.CompletedTask;
        }
    
        public void Dispose() {
            timer?.Dispose();
        }
    }

    IHostedService中,StartAsync()StopAsync() 是必须实现的方法。应用程序准备好时会触发 StartAsync(),在应用程序“优雅地”关闭(原文:graceful shutdown)时,会触发 StopAsync()

    通过修改新建定时器的最后一个参数(代码中为TimeSpan.FromHours(1)) 来改变任务执行的周期,修改第三个参数(代码中为TimeSpan.Zero)来更改程序启动后执行任务的延时——指定为TimeSpan.Zero则在程序启动后立刻执行一次任务。

  3. 在Startup.cs中依赖注入
    public void ConfigureServices(IServiceCollection services) {
        ... ...
    
        services.AddHostedService<WriteToDb>();
        services.AddScoped<DbOperation.IScopedProcessingService, DbOperation.ScopedProcessingService>();
    
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

启动程序,即可在控制台窗口中看到执行的数据库语句:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据