封装 BackgroundService


基类 我在ExecuteAsync中写的是while 也可以增加定时器 看自己需求,while的好处就是在上一次Work没有执行时下一次Work不会执行,定时器的话就是相反不管上一次Work有没有执行完,到下一次执行时间后,都会执行

public abstract class BaseJob : BackgroundService
{
    private readonly ILogger<BaseJob> logger = ServiceLocator.GetService<ILogger<BaseJob>>();
    public BaseJob()
    {
        Key = GetType().Name;
    }
    /// <summary>
    /// Key
    /// </summary>
    public string Key { get; private set; }
    /// <summary>
    /// true为启动 false为停止
    /// </summary>
    private volatile bool _isRunning;
    public bool IsRunning
    {
        get => _isRunning;
        set => _isRunning = value;
    }

    /// <summary>
    /// 信息
    /// </summary>
    public string? JobMsg { get; set; }

    /// <summary>
    /// 停止原因
    /// </summary>
    public string? StopMsg { get; set; }

    /// <summary>
    /// 启动时间
    /// </summary>
    public DateTime? StartTime { get; set; }
    /// <summary>
    /// 停止时间
    /// </summary>
    public DateTime? StopTime { get; set; }
    /// <summary>
    /// 备注
    /// </summary>
    public string? Remark { get; set; }
    /// <summary>
    /// 排序号
    /// </summary>
    public short OrderId { get; set; } = 1;
    /// <summary>
    /// WorkContent
    /// </summary>
    public Func<Task> WorkContent { get; set; }

    /// <summary>
    /// 是否需要轮询 默认需要
    /// </summary>
    public bool IsPoll { get; set; } = true;

    /// <summary>
    /// 轮询间隔时间 默认两秒
    /// </summary>
    public TimeSpan Delay
    {
        get => _delay;
        set
        {
            if (value < TimeSpan.Zero)
                throw new ArgumentException("延迟时间不能为负数");
            _delay = value;
        }
    }
    private TimeSpan _delay = TimeSpan.FromSeconds(2);



    public override async Task StartAsync(CancellationToken cancellationToken)
    {
        StartTime = DateTime.Now;
        if (IsRunning == false)
        {
            StopMsg = string.Empty;
            JobMsg = string.Empty;
            await base.StartAsync(cancellationToken);
        }
    }
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        if (WorkContent == null) throw new ArgumentNullException(nameof(WorkContent), $"key:{Key}的Work为空");
        try
        {
            IsRunning = true;
            logger.LogInformation($"Job {Key} 开始执行");
            
            if (IsPoll)
            {
                while (!stoppingToken.IsCancellationRequested)
                {
                    try 
                    {
                        await WorkContent.Invoke().ConfigureAwait(false);
                        await Task.Delay(Delay, stoppingToken).ConfigureAwait(false);
                        stoppingToken.ThrowIfCancellationRequested();
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, $"Job {Key} 执行出错: {ex.Message}");
                        JobMsg = $"执行出错: {ex.Message}";
                    }
                }
            }
            else
            {
                await WorkContent.Invoke();
                logger.LogInformation($"Job {Key} 单次执行完成");
                JobMsg = "单次Work已结束";
            }
        }
        catch (Exception ex)
        {
            JobMsg = "异常:" + ex.Message;
            await StopAsync(stoppingToken, $"BaseJob捕获到异常已停止:{ex.Message}");
        }
        finally
        {
            IsRunning = false;// 确保 IsRunning 在方法退出时设置为 false
        }
    }

    public virtual async Task StopAsync(CancellationToken cancellationToken, string stopMsg)
    {
        logger.LogError($"Job {Key} 退出");
        StopMsg = stopMsg;
        StopTime = DateTime.Now;
        IsRunning = false;
        await base.StopAsync(cancellationToken);
    }
}

最基本的使用案例 会在控制台两秒输出一次

public class TestJob : BaseJob
{
    public TestJob()
    {
        base.Remark = "测试Job";
        base.WorkContent = () =>
        {
            Console.WriteLine("Test");
            return Task.CompletedTask;
        };
    }
}

另外是对Job的管理查询,启动和停止

public class JobManager
{
    private readonly IServiceProvider _serviceProvider;
    private readonly List<JobInfo> _jobs = new();

    public JobManager(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void AddJob<T>() where T : BaseJob
    {
        //ActivatorUtilities.CreateInstance 方法接受以一个参数:
        //_serviceProvider:这是 IServiceProvider 对象,它提供了创建服务实例的能力。
        //因为已经给T了 所以不需要第二个参数了 而且类也是无参的构造函数
        var job = ActivatorUtilities.CreateInstance<T>(_serviceProvider);
        _jobs.Add(new JobInfo(job));
    }

    public void AddJob(Type jobType)
    {
        //ActivatorUtilities.CreateInstance 方法接受三个参数:
        //_serviceProvider:这是 IServiceProvider 对象,它提供了创建服务实例的能力。
        //第二个参数:这是 Type 对象,表示您想要创建的类的类型。
        //第三个参数:这个参数在创建实例时被传递给构造函数。
        var job = (BaseJob)ActivatorUtilities.CreateInstance(_serviceProvider, jobType);
        _jobs.Add(new JobInfo(job));
    }

    public async Task StartJobAsync(string key, CancellationToken cancellationToken)
    {
        var job = GetJobInfo(key);
        if (job != null) await job.Service.StartAsync(cancellationToken);
    }

    public async Task StartJobAllAsync(CancellationToken cancellationToken)
    {
        foreach (var job in _jobs)
        {
            if (job.IsRunning) continue;
            try
            {
                await job.Service.StartAsync(cancellationToken);
            }
            catch (Exception ex)
            {
                LogHelp.LogError($"{job.Key}启动异常{ex.Message}");
            }
        }
    }

    public async Task StopJobAsync(string key, CancellationToken cancellationToken)
    {
        var job = GetJobInfo(key);
        if (job != null) await job.Service.StopAsync(cancellationToken, "手动停止");
    }

    public async Task StopJobAllAsync(CancellationToken cancellationToken)
    {
        foreach (var job in _jobs) await job.Service.StopAsync(cancellationToken, "手动停止");
    }

    public IEnumerable<JobInfo> GetJobInfos()
    {
        return _jobs.OrderBy(s=>s.Service.OrderId);
    }

    public JobInfo GetJobInfo(string key)
    {
        return _jobs.FirstOrDefault(j => j.Key == key);
    }
}
public static class JobExtension
{
    /// <summary>
    /// 注入JobManager
    /// </summary>
    /// <param name="services"></param>
    public static void AddJobManager(this IServiceCollection services)
    {
        services.AddSingleton<JobManager>();
        var assemblies = AppDomain.CurrentDomain.GetAssemblies();
        var jobTypes = assemblies.SelectMany(a => a.GetTypes())
            .Where(t => typeof(BaseJob).IsAssignableFrom(t) && !t.IsAbstract);

        foreach (var jobType in jobTypes)
        {
            services.AddSingleton(jobType);
        }
    }

    /// <summary>
    /// 添加所有Job 默认启动
    /// </summary>
    /// <param name="app"></param>
    /// <param name="noStartJob">不启动哪些Job</param>
    /// <param name="IsStart">是否启动 默认为true</param>
    /// <returns></returns>
    public static void UseAddJobAll(this IApplicationBuilder app, bool isStart = true, params string[] noStartJob)
    {
        // 获取所有继承自 BaseJob 的子类类型
        var jobTypes = AppDomain.CurrentDomain.GetDerivedTypes<BaseJob>();
        // 获取 JobManager 实例
        var jobManager = app.ApplicationServices.GetRequiredService<JobManager>();

        foreach (var jobType in jobTypes)
        {
            if (noStartJob.Contains(jobType.Name)) continue;
            jobManager.AddJob(jobType);
        }

        if (isStart) jobManager.StartJobAllAsync(CancellationToken.None).GetAwaiter();
    }

    /// <summary>
    /// 添加所有Job 默认启动
    /// </summary>
    /// <param name="app"></param>
    /// <param name="IsStart">是否启动 默认为true</param>
    /// <returns></returns>
    public static void UseAddJobAll(this IApplicationBuilder app, bool isStart = true)
    {
        // 获取所有继承自 BaseJob 的子类类型
        var jobTypes = AppDomain.CurrentDomain.GetDerivedTypes<BaseJob>();
        // 获取 JobManager 实例
        var jobManager = app.ApplicationServices.GetRequiredService<JobManager>();

        foreach (var jobType in jobTypes) jobManager.AddJob(jobType);
        if (isStart) jobManager.StartJobAllAsync(CancellationToken.None).GetAwaiter();
    }

    private static IEnumerable<Type> GetDerivedTypes<T>(this AppDomain appDomain)
    {
        return appDomain.GetAssemblies()
            .SelectMany(assembly => assembly.GetTypes())
            .Where(type => typeof(T).IsAssignableFrom(type) && !type.IsAbstract && type != typeof(T));
    }
}

在WebApi项目Program类中来调用方法

builder.Services.AddJobManager();
app.UseAddJobAll(false,nameof(S1StackerErrorJob), nameof(S2StackerErrorJob));

另外如果有有使用 Masuit.Tools(码数吐司库) 类库的兄弟,请不要使用他的 “ASP.NET Core自动扫描注册服务” 功能,这会导致你继承BackgroundService的类莫名其妙自己启动,有Bug