封装 BackgroundService

打印 上一主题 下一主题

主题 880|帖子 880|积分 2640

基类 我在ExecuteAsync中写的是while 也可以增加定时器 看自己需求,while的好处就是在上一次Work没有执行时下一次Work不会执行,定时器的话就是相反不管上一次Work有没有执行完,到下一次执行时间后,都会执行
  1. public abstract class BaseJob : BackgroundService
  2. {
  3.     private readonly ILogger<BaseJob> logger = ServiceLocator.GetService<ILogger<BaseJob>>();
  4.     public BaseJob()
  5.     {
  6.         Key = GetType().Name;
  7.     }
  8.     /// <summary>
  9.     /// Key
  10.     /// </summary>
  11.     public string Key { get; private set; }
  12.     /// <summary>
  13.     /// true为启动 false为停止
  14.     /// </summary>
  15.     private volatile bool _isRunning;
  16.     public bool IsRunning
  17.     {
  18.         get => _isRunning;
  19.         set => _isRunning = value;
  20.     }
  21.     /// <summary>
  22.     /// 信息
  23.     /// </summary>
  24.     public string? JobMsg { get; set; }
  25.     /// <summary>
  26.     /// 停止原因
  27.     /// </summary>
  28.     public string? StopMsg { get; set; }
  29.     /// <summary>
  30.     /// 启动时间
  31.     /// </summary>
  32.     public DateTime? StartTime { get; set; }
  33.     /// <summary>
  34.     /// 停止时间
  35.     /// </summary>
  36.     public DateTime? StopTime { get; set; }
  37.     /// <summary>
  38.     /// 备注
  39.     /// </summary>
  40.     public string? Remark { get; set; }
  41.     /// <summary>
  42.     /// 排序号
  43.     /// </summary>
  44.     public short OrderId { get; set; } = 1;
  45.     /// <summary>
  46.     /// WorkContent
  47.     /// </summary>
  48.     public Func<Task> WorkContent { get; set; }
  49.     /// <summary>
  50.     /// 是否需要轮询 默认需要
  51.     /// </summary>
  52.     public bool IsPoll { get; set; } = true;
  53.     /// <summary>
  54.     /// 轮询间隔时间 默认两秒
  55.     /// </summary>
  56.     public TimeSpan Delay
  57.     {
  58.         get => _delay;
  59.         set
  60.         {
  61.             if (value < TimeSpan.Zero)
  62.                 throw new ArgumentException("延迟时间不能为负数");
  63.             _delay = value;
  64.         }
  65.     }
  66.     private TimeSpan _delay = TimeSpan.FromSeconds(2);
  67.     public override async Task StartAsync(CancellationToken cancellationToken)
  68.     {
  69.         StartTime = DateTime.Now;
  70.         if (IsRunning == false)
  71.         {
  72.             StopMsg = string.Empty;
  73.             JobMsg = string.Empty;
  74.             await base.StartAsync(cancellationToken);
  75.         }
  76.     }
  77.     protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  78.     {
  79.         if (WorkContent == null) throw new ArgumentNullException(nameof(WorkContent), $"key:{Key}的Work为空");
  80.         try
  81.         {
  82.             IsRunning = true;
  83.             logger.LogInformation($"Job {Key} 开始执行");
  84.             
  85.             if (IsPoll)
  86.             {
  87.                 while (!stoppingToken.IsCancellationRequested)
  88.                 {
  89.                     try
  90.                     {
  91.                         await WorkContent.Invoke().ConfigureAwait(false);
  92.                         await Task.Delay(Delay, stoppingToken).ConfigureAwait(false);
  93.                         stoppingToken.ThrowIfCancellationRequested();
  94.                     }
  95.                     catch (Exception ex)
  96.                     {
  97.                         logger.LogError(ex, $"Job {Key} 执行出错: {ex.Message}");
  98.                         JobMsg = $"执行出错: {ex.Message}";
  99.                     }
  100.                 }
  101.             }
  102.             else
  103.             {
  104.                 await WorkContent.Invoke();
  105.                 logger.LogInformation($"Job {Key} 单次执行完成");
  106.                 JobMsg = "单次Work已结束";
  107.             }
  108.         }
  109.         catch (Exception ex)
  110.         {
  111.             JobMsg = "异常:" + ex.Message;
  112.             await StopAsync(stoppingToken, $"BaseJob捕获到异常已停止:{ex.Message}");
  113.         }
  114.         finally
  115.         {
  116.             IsRunning = false;// 确保 IsRunning 在方法退出时设置为 false
  117.         }
  118.     }
  119.     public virtual async Task StopAsync(CancellationToken cancellationToken, string stopMsg)
  120.     {
  121.         logger.LogError($"Job {Key} 退出");
  122.         StopMsg = stopMsg;
  123.         StopTime = DateTime.Now;
  124.         IsRunning = false;
  125.         await base.StopAsync(cancellationToken);
  126.     }
  127. }
复制代码
最基本的使用案例 会在控制台两秒输出一次
  1. public class TestJob : BaseJob
  2. {
  3.     public TestJob()
  4.     {
  5.         base.Remark = "测试Job";
  6.         base.WorkContent = () =>
  7.         {
  8.             Console.WriteLine("Test");
  9.             return Task.CompletedTask;
  10.         };
  11.     }
  12. }
复制代码
另外是对Job的管理查询,启动和停止
  1. public class JobManager
  2. {
  3.     private readonly IServiceProvider _serviceProvider;
  4.     private readonly List<JobInfo> _jobs = new();
  5.     public JobManager(IServiceProvider serviceProvider)
  6.     {
  7.         _serviceProvider = serviceProvider;
  8.     }
  9.     public void AddJob<T>() where T : BaseJob
  10.     {
  11.         //ActivatorUtilities.CreateInstance 方法接受以一个参数:
  12.         //_serviceProvider:这是 IServiceProvider 对象,它提供了创建服务实例的能力。
  13.         //因为已经给T了 所以不需要第二个参数了 而且类也是无参的构造函数
  14.         var job = ActivatorUtilities.CreateInstance<T>(_serviceProvider);
  15.         _jobs.Add(new JobInfo(job));
  16.     }
  17.     public void AddJob(Type jobType)
  18.     {
  19.         //ActivatorUtilities.CreateInstance 方法接受三个参数:
  20.         //_serviceProvider:这是 IServiceProvider 对象,它提供了创建服务实例的能力。
  21.         //第二个参数:这是 Type 对象,表示您想要创建的类的类型。
  22.         //第三个参数:这个参数在创建实例时被传递给构造函数。
  23.         var job = (BaseJob)ActivatorUtilities.CreateInstance(_serviceProvider, jobType);
  24.         _jobs.Add(new JobInfo(job));
  25.     }
  26.     public async Task StartJobAsync(string key, CancellationToken cancellationToken)
  27.     {
  28.         var job = GetJobInfo(key);
  29.         if (job != null) await job.Service.StartAsync(cancellationToken);
  30.     }
  31.     public async Task StartJobAllAsync(CancellationToken cancellationToken)
  32.     {
  33.         foreach (var job in _jobs)
  34.         {
  35.             if (job.IsRunning) continue;
  36.             try
  37.             {
  38.                 await job.Service.StartAsync(cancellationToken);
  39.             }
  40.             catch (Exception ex)
  41.             {
  42.                 LogHelp.LogError($"{job.Key}启动异常{ex.Message}");
  43.             }
  44.         }
  45.     }
  46.     public async Task StopJobAsync(string key, CancellationToken cancellationToken)
  47.     {
  48.         var job = GetJobInfo(key);
  49.         if (job != null) await job.Service.StopAsync(cancellationToken, "手动停止");
  50.     }
  51.     public async Task StopJobAllAsync(CancellationToken cancellationToken)
  52.     {
  53.         foreach (var job in _jobs) await job.Service.StopAsync(cancellationToken, "手动停止");
  54.     }
  55.     public IEnumerable<JobInfo> GetJobInfos()
  56.     {
  57.         return _jobs.OrderBy(s=>s.Service.OrderId);
  58.     }
  59.     public JobInfo GetJobInfo(string key)
  60.     {
  61.         return _jobs.FirstOrDefault(j => j.Key == key);
  62.     }
  63. }
复制代码
  1. public static class JobExtension
  2. {
  3.     /// <summary>
  4.     /// 注入JobManager
  5.     /// </summary>
  6.     /// <param name="services"></param>
  7.     public static void AddJobManager(this IServiceCollection services)
  8.     {
  9.         services.AddSingleton<JobManager>();
  10.         var assemblies = AppDomain.CurrentDomain.GetAssemblies();
  11.         var jobTypes = assemblies.SelectMany(a => a.GetTypes())
  12.             .Where(t => typeof(BaseJob).IsAssignableFrom(t) && !t.IsAbstract);
  13.         foreach (var jobType in jobTypes)
  14.         {
  15.             services.AddSingleton(jobType);
  16.         }
  17.     }
  18.     /// <summary>
  19.     /// 添加所有Job 默认启动
  20.     /// </summary>
  21.     /// <param name="app"></param>
  22.     /// <param name="noStartJob">不启动哪些Job</param>
  23.     /// <param name="IsStart">是否启动 默认为true</param>
  24.     /// <returns></returns>
  25.     public static void UseAddJobAll(this IApplicationBuilder app, bool isStart = true, params string[] noStartJob)
  26.     {
  27.         // 获取所有继承自 BaseJob 的子类类型
  28.         var jobTypes = AppDomain.CurrentDomain.GetDerivedTypes<BaseJob>();
  29.         // 获取 JobManager 实例
  30.         var jobManager = app.ApplicationServices.GetRequiredService<JobManager>();
  31.         foreach (var jobType in jobTypes)
  32.         {
  33.             if (noStartJob.Contains(jobType.Name)) continue;
  34.             jobManager.AddJob(jobType);
  35.         }
  36.         if (isStart) jobManager.StartJobAllAsync(CancellationToken.None).GetAwaiter();
  37.     }
  38.     /// <summary>
  39.     /// 添加所有Job 默认启动
  40.     /// </summary>
  41.     /// <param name="app"></param>
  42.     /// <param name="IsStart">是否启动 默认为true</param>
  43.     /// <returns></returns>
  44.     public static void UseAddJobAll(this IApplicationBuilder app, bool isStart = true)
  45.     {
  46.         // 获取所有继承自 BaseJob 的子类类型
  47.         var jobTypes = AppDomain.CurrentDomain.GetDerivedTypes<BaseJob>();
  48.         // 获取 JobManager 实例
  49.         var jobManager = app.ApplicationServices.GetRequiredService<JobManager>();
  50.         foreach (var jobType in jobTypes) jobManager.AddJob(jobType);
  51.         if (isStart) jobManager.StartJobAllAsync(CancellationToken.None).GetAwaiter();
  52.     }
  53.     private static IEnumerable<Type> GetDerivedTypes<T>(this AppDomain appDomain)
  54.     {
  55.         return appDomain.GetAssemblies()
  56.             .SelectMany(assembly => assembly.GetTypes())
  57.             .Where(type => typeof(T).IsAssignableFrom(type) && !type.IsAbstract && type != typeof(T));
  58.     }
  59. }
复制代码
在WebApi项目Program类中来调用方法
  1. builder.Services.AddJobManager();
  2. app.UseAddJobAll(false,nameof(S1StackerErrorJob), nameof(S2StackerErrorJob));
复制代码
另外如果有有使用 Masuit.Tools(码数吐司库) 类库的兄弟,请不要使用他的 “ASP.NET Core主动扫描注册服务” 功能,这会导致你继承BackgroundService的类莫名其妙自己启动,有Bug


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

河曲智叟

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表