封装 BackgroundService

news/2025/1/24 9:33:15/文章来源:https://www.cnblogs.com/cyfj/p/18689044

基类 我在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));

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/874605.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

天通ERP S系列如何设置根据不同字段区分商品显示汇总,且在两个表中显示

展示效果:操作方式: 1、任我行打印管理器,点击右上角添加公式按钮,添加一个字段名称为空、字符类型为字符数据,且表达内容为空的公式字段保存(英文输入法下两个’’)2、在表头的最后添加一列,绑定刚才添加的空的公式字段,并绑定新增的字段为分单总计 且该字段取消显示边框 3、…

kettle从入门到精通 第九十一课 ETL之kettle http接口下载文件流

1、场景需求 群里一位老朋友想通过http接口下载文件流,然后将文件流保存为文件存储到本地,如下图所示: 2、做过应用程序研发,对http知识有所了解的,结合对方发的postman截图,一眼就知道了接口的真实面目。接口返回的content-type是application/octet-stream且有文件下载说…

搭建latex服务

1.领取免费服务器,推荐免费服务器(SanFengYun)见下图。2.安装宝塔面板,配置内网为127.0.0.1,访问外网地址。 3.可以在宝塔面板一键部署网站,输入自己的域名即可。 4.关键:安装docker,安装yum,设置github可以访问。 5.更换docker镜像,自带镜像无法访问 6.按照overleaf…

Cisco ASAv 9.22.1.3 - 思科自适应安全虚拟设备 (ASAv)

Cisco ASAv 9.22.1.3 - 思科自适应安全虚拟设备 (ASAv)Cisco ASAv 9.22.1.3 - 思科自适应安全虚拟设备 (ASAv) Cisco Adaptive Security Virtual Appliance (ASAv) 请访问原文链接:https://sysin.org/blog/cisco-asav/ 查看最新版。原创作品,转载请保留出处。 作者主页:sysi…

大文件跨国传输最优方案推荐,实现可靠稳定准确传输!

大文件跨国传输的需求广泛存在于多个行业中,这些行业对于数据的传输速度、安全性、稳定性和高效性都有着较高的要求。比如医疗机构在跨国医疗合作中需要传输医疗影像资料、诊断报告等;基建企业在海外的基础设施建设中,也包含数据出海环节,如通信运营商在海外建设网络时,需…

重拾 SSH:从基础到安全加固

安全外壳协议(Secure Shell Protocol,简称SSH)是一种加密的网络传输协议,属于应用层协议。OpenSSH 是最流行的 SSH 实现,它是大量操作系统的默认组件 OpenSSH 套件由以下工具组成:远程操作使用:ssh, scp 和 sftp 密钥管理:ssh-add, ssh-keysign, ssh-keyscan 和 ssh-ke…

史上第一位0:3夺得冠军

以颤抖之身追赶,怀敬畏之心挑战。--《棋魂》

【推荐】一款.NET Core开发的开源免费功能完善的医疗影像PACS系统

项目介绍 今天给大家推荐一款开源(MIT License开源协议)、免费、完善、轻量级的医疗影像PACS系统,基于.NET Core 的 DICOM SCP(Service Class Provider)服务器,提供 DICOM 存储、工作列表、查询检索服务,打印服务,WADO/DicomWeb服务,集成了功能强大的DICOM 桌面和Web查…

C# as 和 is 运算符区别和用法

前言 在C#中,as 和 is 关键字都用于处理类型转换的运算符,但它们有不同的用途和行为。本文我们将详细解释这两个运算符的区别和用法。 is 运算符 is 运算符用于检查对象是否是某个特定类型,或者是否可以转换为该类型。它返回一个布尔值 (true 或 false)。string title = &qu…

2025.1.24

树上背包DESTRUCTION 3,2,109 年论文现在才学也是没救了。 对于重量为 \(1\) 的,直接每次枚举 size 就可以做到 \(n^2\),证明考虑一个点对只会在 lca 出有一个贡献。 考虑重量是 \(v_i\),定义 \(v_i\) 的上界是 \(V\)。 暴力做是 \(nV^2\) 的,实在是不优美。 考虑更改更新顺…

读量子霸权13逆转衰老

热力学定律解释衰老,开放系统或可逆转。量子计算机或助解决衰老,实现生物与数字永生。热量限制、DNA修复、细胞重编程等或延长寿命,但需防副作用。1. 热力学第二定律 1.1. 热力学三大定律1.1.1. 第一定律简单地指出,物质和能量的总量是一个常数,能量不会从无到有,总是守恒…

更改暗盒DX码-DIY胶卷DX码

更改暗盒DX码-DIY胶卷DX码我们平时用纯机械相机可能更多一些,纯机械相机多数也不具备读取DX码这些信息的功能。有些傻瓜相机或自动相机需要读取这些信息,如果不能正确识别,有时候我们使用分装卷,暗盒都是二次利用的,因此DX码也是不正确的,机器读取出来就会造成曝光错误。…