.NET-服务承载系统(Hosting)(支持Cron表达式)

简介

.NET Core提供了承载(Hosting)系统,我们可以在它之上寄宿多个长时间运行的服务,ASP.NET Core应用仅仅是该承载系统的一种典型的服务类型而已,任何需要在后台长时间运行的操作都可以定义成标准化的服务并利用该系统来承载。
ASP.NET Core框架目前存在两个承载(Hosting)系统。ASP.NET Core最初提供了一个以IWebHostBuilder/IWebHost为核心的承载系统,ASP.NET Core 3依然支持这样的应用承载方式,已过时不介绍;

除了承载Web应用本身,我们还有针对后台服务的承载需求,为此微软推出了以IHostBuilder/IHost为核心的承载系统。实际上,Web应用本身就是一个长时间运行的后台服务,我们完全可以定义一个承载服务,从而将Web应用承载于这个系统中。如下图所示,这个用来承载ASP.NET Core应用的承载服务类型为GenericWebHostService,这是一个实现了IHostedService接口的内部类型。

重要接口

IHostedService接口表示承载服务。
IHost接口表示承载服务的宿主。

承载服务(IHostedService)

承载服务通过IHostedService接口表示,该接口定义的StartAsync方法和StopAsync方法可以启动与关闭服务。
一个ASP.NET Core应用本质上是一个需要长时间运行的服务,开启这个服务是为了启动一个网络监听器。当监听到抵达的HTTP请求之后,该监听器会将请求传递给应用提供的管道进行处理。管道完成了对请求处理之后会生成HTTP响应。
这个用来承载ASP.NET Core应用的承载服务类型为GenericWebHostService,这是一个实现了IHostedService接口的内部类型。

当作为宿主的IHost对象被启动的时候,它会利用依赖注入框架激活每个注册的IHostedService服务,并通过调用StartAsync方法来启动它们。当服务承载应用程序关闭的时候,作为服务宿主的IHost对象会被关闭,由它承载的每个IHostedService服务对象的StopAsync方法也随之被调用。

public interface IHostedService
    {
        Task StartAsync(CancellationToken cancellationToken);
        Task StopAsync(CancellationToken cancellationToken);
    }

注册承载服务有以下两种方式,他们是等价的:

serviceCollection.AddSingleton<IHostedService, xxxxBackService>();
//由于该方法通过调用TryAddEnumerable扩展方法来注册服务,所以不用担心服务重复注册的问题
serviceCollection.AddHostedService<xxxxBackService>();

依赖注入

服务承载系统无缝整合了依赖注入框架。承载服务被注册到依赖注入框架中了。既然承载服务实例最终是通过依赖注入框架提供的,那么它自身所依赖的服务当然也可以注册到依赖注入框架中。
由于承载服务大都需要长时间运行直到应用被关闭,所以针对承载服务的注册一般采用Singleton生命周期模式。

Demo

实现一个单线程定时轮询服务
core2.1开始,提供了一个继承IHostedService接口的实现类BackgroundService。

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace MeShop.View.Config.Models
{
    public abstract class IntervalBackgroundService : BackgroundService
    {
        //间隔时间(s)
        private int _intervalSeconds = 60;
        //通知是否正在执行,如果在执行,不允许下
        private static volatile bool NoticeRunning = true;
        private ILogger<IntervalBackgroundService> _logger = null;
        public IntervalBackgroundService(int seconds, ILogger<IntervalBackgroundService> logger)
        {
            this._intervalSeconds = seconds * 60 * 1000;
            this._logger = logger;
        }
        protected override Task ExecuteAsync(CancellationToken cancellationToken)
        {
            var t = new Thread((obj) =>
            {
                while (true)
                {
                    try
                    {
                        if (!cancellationToken.IsCancellationRequested)
                        {
                            if (NoticeRunning)
                            {
                                NoticeRunning = false;
                                DoWork(obj);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        string message = null;
                        if (ex is AggregateException)
                        {
                            if (ex.InnerException != null)
                            {
                                message = ex.InnerException.Message;
                            }
                        }
                        else
                        {
                            message = ex.Message;
                        }
                        this._logger.LogError($"IntervalBackgroundService,Message:{message}");
                    }
                    finally
                    {
                        NoticeRunning = true;
                    }
                    Thread.Sleep(this._intervalSeconds);
                }
            });
            t.IsBackground = true;
            t.Start(cancellationToken);
            return Task.CompletedTask;
        }
        protected abstract void DoWork(Object state);
    }
}

承载服务的宿主(IHost)

承载服务最终被承载于通过IHost接口表示的宿主上。一般来说,一个服务承载应用在整个生命周期内只会创建一个IHost对象,我们启动和关闭应用程序本质上就是启动和关闭作为宿主的IHost对象。
IHost接口的Services属性返回作为依赖注入容器的IServiceProvider对象,该对象提供了服务承载过程中所需的服务实例,其中就包括需要承载的IHostedService服务。

public interface IHost : IDisposable
{
    IServiceProvider Services { get; }
    Task StartAsync(CancellationToken cancellationToken = default);
    Task StopAsync(CancellationToken cancellationToken = default);
}

Run扩展方法

如果我们调用IHost对象的扩展方法Run,它会在内部调用StartAsync方法,接下来它会持续等待下去直到接收到应用被关闭的通知。当IHost对象对象利用IHostApplicationLifetime服务接收到关于应用关闭的通知后,它会调用自身的StopAsync方法,针对Run方法的调用此时才会返回。启动IHost对象直到应用关闭这一实现体现在如下这个WaitForShutdownAsync扩展方法上。

public static class HostingAbstractionsHostExtensions
{

    public static async Task WaitForShutdownAsync(this IHost host, CancellationToken token = default)
    {
        var applicationLifetime = host.Services.GetService<IHostApplicationLifetime>();
        token.Register(state => ((IHostApplicationLifetime)state).StopApplication(), applicationLifetime);

        var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
        applicationLifetime.ApplicationStopping.Register(state =>
        {
            var tcs = (TaskCompletionSource<object>)state;
            tcs.TrySetResult(null);
        }, waitForStop);

        await waitForStop.Task;
        await host.StopAsync();
    }
}

配置

IHostBuilder接口针对配置系统的设置体现在ConfigureHostConfiguration和ConfigureAppConfiguration方法上。通过前面的实例演示,我们知道ConfigureHostConfiguration方法涉及的配置主要是在服务承载过程中使用的,是针对服务宿主的配置;ConfigureAppConfiguration方法设置的则是供承载的IHostedService服务使用的,是针对应用的配置。不过前者最终会合并到后者之中,我们最终得到的配置实际上是两者合并的结果。

日志

调用IHostBuilder接口的ConfigureLogging扩展方法注册了日志框架的核心服务,并利用提供的Action对象注册了针对控制台作为输出渠道的ConsoleLoggerProvider。

new HostBuilder()
            .ConfigureLogging(builder => builder.AddConsole())
            .Build()
            .Run();

支持Cron表达式、间隔时间的工具(TaskScheduler)

间隔时间后台服务

  public class IntervalBackgroundService : BackgroundService
    {
        private readonly ILogger<IntervalBackgroundService> _logger = null;
        public IntervalBackgroundService(ILogger<IntervalBackgroundService> logger)
        {
            _logger = logger;
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                await SpareTime.DoAsync(1000, () =>
                {
                    _logger.LogInformation("Interval Worker running at: {time}", DateTimeOffset.Now);
                }, stoppingToken);
            }
        }
    }

Cron表达式后台服务

 public class CronBackgroundService : BackgroundService
    {
        private readonly ILogger<CronBackgroundService> _logger = null;
        public CronBackgroundService(ILogger<CronBackgroundService> logger)
        {
            _logger = logger;
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                // 执行 Cron 表达式任务
                await SpareTime.DoAsync("*/5 * * * * *", () =>
                {
                    _logger.LogInformation("Cron Worker running at: {time}", DateTimeOffset.Now);
                }, stoppingToken, CronFormat.IncludeSeconds);
            }
        }
    }

 NET Core项目启动时执行异步定时任务

1、写一个任务服务类继承BackgroundService

public class APIDataService : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    //需要执行的任务

                }
                catch (Exception ex)
                {
                    LogHelper.Error(ex.Message);
                }
                await Task.Delay(1000, stoppingToken);//等待1秒
            }
        }
    }

2、在Startup.cs中注入

public void ConfigureServices(IServiceCollection services)
{
       ......
  services.AddSingleton<Microsoft.Extensions.Hosting.IHostedService, APIDataService>();
}
原文地址:https://www.cnblogs.com/netlock/p/14911499.html