5

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

 2 years ago
source link: https://azhuge233.com/asp-net-core-%e9%80%9a%e8%bf%87-ihostedservice-%e5%ae%9e%e7%8e%b0%e5%90%8e%e5%8f%b0%e4%bb%bb%e5%8a%a1%e8%bf%90%e8%a1%8c/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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();
    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();
    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);
    public void ConfigureServices(IServiceCollection services) {
        ... ...
    
        services.AddHostedService<WriteToDb>();
        services.AddScoped<DbOperation.IScopedProcessingService, DbOperation.ScopedProcessingService>();
    
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

启动程序,即可在控制台窗口中看到执行的数据库语句:Snipaste_2019-03-26_17-30-00-768x123.png

.NET, 所有.NET Core, ASP.NET

发表评论 取消回复

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

评论

显示名称 *

电子邮箱地址 *

网站地址

通过邮件通知我后续评论

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK