有段日子没有更新,写点东西冒个泡 。这篇文章过来讲个小东西,也是大家在日常开发中也经常需要面临的问题:后台定时任务处理。估计大家看到这句就已经联想到 QuartZ 等类似第三方类库了,不好意思,后边的事情和它们没有关系。这里要展开的是用.Net Core 下的 Generic Host 配合封装简版定时任务处理框架的过程。至于什么是Generic Host,简单来说就是一个简化版不含Http管道等的非Web应用托管宿主服务,至于它如何来,其内有着什么样的实现细节,官方介绍已经足够。这篇文章主要还是回到实际的基础封装过程实现层面,用一个小东西来演示如何在常见业务代码中梳理职责,内容主要如下:

1.  概要分解

2.  封装实现

3.   示例演示

4.   注意事项

 一. 概要分解

  如果对Generic Host 已经有了解的同学可能也看过网上其他文章,大多也都介绍用它如何实现定时任务处理。这些文章基本提供了一个通用实现,对业务实现还是稍显啰嗦。这两天整理逻辑有个任务不得不临时定时处理,想到这个东西,花了点时间处理了下,东西不复杂不过还是想把这个思路分享给需要的朋友。

  定时任务,分解来看特别简单,就是两个维度“  定时 +  任务 ”,如果还有另外一个维度,那就是 任务运行的托管服务。在托管平台上添加定时规则,根据规则触发任务,工作结束。

  1.  关于定时,主要就是一套任务触发的规则,其作为一个调度者,只需要关心的是 在什么时间,以何种频率 触发任务。   在.Net 下我们通过定时器(Timer - 构造函数包含这两个核心参数,.net 下有两个Timer实现,一个是System.Timer.Timer,一个是System.Threading.Timer, 这里用的第二者,自由度更高)来实现,但是它不应该直接和具体的任务挂钩,使用方也不应该每次都自己来处理Timer的初始化及相关回收释放等相同操作,我们需要的是使用方只需告知框架层要执行什么任务,和任务对应的时间规则。

  2.  关于任务, 这个角色是一个任务的执行者, 定时调度者 告诉 任务执行者 在什么时候开始执行和结束任务,其本身不会关注调度的实现。

  3.  关于托管服务,也就是已经说过的Generic Host,当然你也可以使用windows服务等。它的职责就是保证给任务提供执行环境,并告诉任务定时器当前服务在什么时候开始运行和关闭。  实现时提供了统一 IHostedService  接口,具体实现下边实现会有展示。Generic Host 启动方式有两种形式:

    a. 如果是.NetCore 站点,默认已经包含,只需要在 ConfigureServices 时注册具体实现即可。

    b. 可以独立创建,比如控制台通过 new HostBuilder() 形式启动,具体参见官方文档。

  为了更直观的展示相关之间的关系,这里我画了个类图来分解相关的职责,同时也是后边具体实现的主要内容:

 二.  封装实现

  从上边类图可以看出当前基础框架主要由 BaseJobTrigger(触发器基类),IJobExcutor(任务执行者接口),ListJobExcutor<IType>(通用列表循环任务执行者基类)。下边分别就上边三者贴出具体实现。

  1.  BaseJobTrigger(触发器基类),实现代码如下:

public abstract class BaseJobTrigger
: IHostedService, IDisposable
{
private Timer _timer;
private readonly TimeSpan _dueTime;
private readonly TimeSpan _periodTime; private readonly IJobExecutor _jobExcutor; /// <summary>
/// 构造函数
/// </summary>
/// <param name="dueTime">到期执行时间</param>
/// <param name="periodTime">间隔时间</param>
/// <param name="jobExcutor">任务执行者</param>
protected BaseJobTrigger(TimeSpan dueTime,
TimeSpan periodTime,
IJobExecutor jobExcutor)
{
_dueTime = dueTime;
_periodTime = periodTime;
_jobExcutor = jobExcutor;
} #region 计时器相关方法 private void StartTimerTrigger()
{
if (_timer == null)
_timer = new Timer(ExcuteJob,_jobExcutor,_dueTime, _periodTime);
else
_timer.Change(_dueTime, _periodTime);
} private void StopTimerTrigger()
{
_timer?.Change(Timeout.Infinite, Timeout.Infinite);
} private void ExcuteJob(object obj)
{
try
{
var excutor = obj as IJobExecutor;
excutor?.StartJob();
}
catch (Exception e)
{
LogUtil.Error($"执行任务({nameof(GetType)})时出错,信息:{e}");
}
}
#endregion /// <summary>
/// 系统级任务执行启动
/// </summary>
/// <returns></returns>
public virtual Task StartAsync(CancellationToken cancellationToken)
{
try
{
StartTimerTrigger();
}
catch (Exception e)
{
LogUtil.Error($"启动定时任务({nameof(GetType)})时出错,信息:{e}");
}
return Task.CompletedTask;
} /// <summary>
/// 系统级任务执行关闭
/// </summary>
/// <returns></returns>
public virtual Task StopAsync(CancellationToken cancellationToken)
{
try
{
_jobExcutor.StopJob();
StopTimerTrigger();
}
catch (Exception e)
{
LogUtil.Error($"停止定时任务({nameof(GetType)})时出错,信息:{e}");
}
return Task.CompletedTask;
} public void Dispose()
{
_timer?.Dispose();
}
}

  这个主要是完成对定时器的封装,StartAsync和StopAsync 为 IHostService 系统服务接口,表示托管服务的开始和结束。

   2. IJobExcutor(任务执行者接口)

public interface IJobExecutor
{
/// <summary>
/// 开始任务
/// </summary>
void StartJob(); /// <summary>
/// 结束任务
/// </summary>
void StopJob();
}

  3. ListJobExcutor<IType>(通用列表循环任务执行者基类)

public abstract class ListJobExcutor<IType> 
                : IJobExecutor
{
/// <summary>
/// 运行状态
/// </summary>
public bool IsRuning { get;protected set; }
/// <summary>
/// 开始任务
/// </summary>
public void StartJob()
{
// 任务依然在执行中,不需要再次唤起
if (IsRuning)
return; IsRuning = true;
IList<IType> list = null; // 结清实体list
do
{
for (var i = 0; IsRuning && i < list?.Count;i++)
{
ExcuteItem(list[i],i);
}
list = GetExcuteSource(); } while (IsRuning && list?.Count > 0);
IsRuning = false;
}
public void StopJob()
{
IsRuning = false;
} /// <summary>
/// 获取list数据源
/// </summary>
/// <returns></returns>
protected virtual IList<IType> GetExcuteSource()
{
return null;
}
/// <summary>
/// 个体任务执行
/// </summary>
/// <param name="item">单个实体</param>
/// <param name="index">在数据源中的索引</param>
protected virtual void ExcuteItem(IType item,int index)
{
}
}

  这个是通用列表循环执的基础封装,因为业务中需要定时处理的大多是需要从数据库或文件批量获取数据,执行处理,例如到期提醒,定时清理超时订单等场景。

  其主要功能实现是 从 GetExcuteSource() 获取执行数据源,循环并通过 ExcuteItem() 执行个体任务,直到没有数据源返回,则此次任务执行结束,等待下次任务触发。如果当次执行时间过长,超过计时器时间间隔,重复触发时 当前任务还在进行中,则不做任何处理。如果数据量过大需要并发执行,子类可以在  ExcuteItem 中异步处理。这样既可保证并发顺序执行。

 三. 示例演示

  以上三个元素就构成了当前定时任务的主要基础框架,在实际处理一个任务的过程中,我们需要定义一个执行者(XXXJobExcutor),一个触发器(XXXJobTrigger,构造函数传入触发时间,间隔,执行者)即可。这里用两个示例来做演示

  1. 基础任务处理

public class TestJobTrigger:BaseJobTrigger
{
public TestJobTrigger() :
base(TimeSpan.Zero,
TimeSpan.FromMinutes(10),
new TestJobExcutor())
{
}
}
public class TestJobExcutor
: IJobExecutor
{
public void StartJob()
{
LogUtil.Info("执行任务!");
} public void StopJob()
{
LogUtil.Info("系统终止任务");
}
}

  以上实现了TestJobTrigger 做任务触发器,十分钟执行一次。TestJobExcutor 作为具体执行者,做任务处理。启动时只需在Startup.cs 中的ConfigureServices方法中添加如下代码即可:

services.AddHostedService<TestJobTrigger>();

  2.  列表循环处理

public class ListJobTrigger
: BaseJobTrigger
{
public ListJobTrigger() :
base(TimeSpan.Zero,
TimeSpan.FromMinutes(10),
new ListJobExcutor())
{ }
} public class ListJobExcutor
: ListJobExcutor<string>
{
private int _page = 0; protected override IList<string> GetExcuteSource()
{
if (_page==0)
{
_page++;
return new List<string>{ "1", "2", "3" };
}
return null;
} protected override void ExcuteItem(string item, int index)
{
LogUtil.Info(item);
}
}

  这个示例定时获取字符串列表,并打印。一样在Startup中注册即可。    

 四.   注意事项

  1. 关于何时使用定时任务的问题

   之所以要说这个问题,是因为我看过不少同学把定时任务这种方式当成万能胶,哪里有缝往哪贴,一个不行起两个。其实有很多场景都可以通过其关联事件加消息队列来完成,比如发短信,接收发送请求后塞消息队列并返回请求方接收成功,队列消费者来负责和短信服务商接口交互。只有对一些对时间属性有要求的处理,咱们通过定时任务等处理,如.....会员生日提醒....

  2. 关于框架元素在解决方案的引用放置

  一个建议: IJobExcutor,ListJobExcutor<IType> 可以放置在通用类库中,BaseJobTrigger,因为其依赖IHostService 放置在站点目录下比较合适。

  3. 关于GenericHost的生存周期问题

  如果你使用的是控制台启动,则此问题暂时可以忽略。

  如果你使用的是站点项目,并且还是通过IIS启动,那么你可能要注意了,因为.net core 的站点自身是有HOST宿主处理,IIS是其上代理,其启动关闭,端口映射等由IIS内部完成。所以其依然受限于IIS的闲置回收影响,当IIS闲置回收时,其后的.Net Host也会被一同关闭,需要有新的请求进来时才会再次启动。不过鉴于当前任务处理已经如此简单,有个取巧的做法,实现一个站点自身的心跳检测任务,IIS默认20分钟回收,任务时间可以设为15分钟(你也可以设置IIS站点回收时间),当然如果你的任务如果没有那么严格的时间要求你也可以不用处理,因为回收后一旦接受到新的请求,任务会再次发起。

  如果你已经看到这里,并且感觉还行的话可以在下方点个赞,或者也可以关注我的公总号(见二维码)

_________________________________________

.Net Core 简单定时任务框架封装的更多相关文章

  1. 仿照jQuery进行一些简单的框架封装(欢迎指教~)

    (function(window,undefined){ var arr = [], push = arr.push, slice = arr.slice; //首先要做的就是封装一个parseHtm ...

  2. .net core的定时任务框架Timed Job

    参考文档:http://www.1234.sh/post/pomelo-extensions-timed-jobs 在该文档中介绍了怎么使用timed job,但是在使用db的时候会发生错误,错误一般 ...

  3. .NET core Quartz 定时任务框架 demo

    开始先建个空的web项目. 创建一个新类 QuartzFactory 狠狠的复制就完事了. public class QuartzFactory : IJobFactory { private rea ...

  4. .NET 跨平台RPC框架DotNettyRPC Web后台快速开发框架(.NET Core) EasyWcf------无需配置,无需引用,动态绑定,轻松使用 C# .NET 0配置使用Wcf(半成品) C# .NET Socket 简单实用框架 C# .NET 0命令行安装Windows服务程序

    .NET 跨平台RPC框架DotNettyRPC   DotNettyRPC 1.简介 DotNettyRPC是一个基于DotNetty的跨平台RPC框架,支持.NET45以及.NET Standar ...

  5. 基于.NET CORE微服务框架 -surging的介绍和简单示例 (开源)

    一.前言 至今为止编程开发已经11个年头,从 VB6.0,ASP时代到ASP.NET再到MVC, 从中见证了.NET技术发展,从无畏无知的懵懂少年,到现在的中年大叔,从中的酸甜苦辣也只有本人自知.随着 ...

  6. Quartz.Net的使用(简单配置方法)定时任务框架

    Quartz.dll 安装nuget在线获取dll包管理器,从中获取最新版 Quartz.Net是一个定时任务框架,可以实现异常灵活的定时任务,开发人员只要编写少量的代码就可以实现“每隔1小时执行”. ...

  7. ASP.NET Core 使用 EF 框架查询数据 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 使用 EF 框架查询数据 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 使用 EF 框架查询数据 上一章节我们学习了如何设置 ...

  8. ASP.NET Core 配置 EF 框架服务 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 配置 EF 框架服务 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 配置 EF 框架服务 上一章节中我们了解了 Entity ...

  9. C# .NET Socket 简单实用框架,socket组件封装

    参考资料 https://www.cnblogs.com/coldairarrow/p/7501645.html 根据.NET Socket 简单实用框架进行了改造,这个代码对socket通信封装还是 ...

随机推荐

  1. xbee/xbeeRPOS1、xbee/xbeePROS2C802.15.4/Digimesh功能方法

    Digi XBee 802.15.4的第一个版本也称为S1,是基于Freescale的无线收发器片子设计的.最新的802.15.4模块(内部称号S1B)采用和Digi ZigBee模块相同SOC芯片设 ...

  2. Oracle 异常 中文乱码

    环境变量 NLS_LANG SIMPLIFIED CHINESE_CHINA.ZHS16GBK

  3. 第三周Access的总结

    一.问;这节课你学到了什么知识? 答:这周我学得比较少,主要是学Access的数据库进行基本的维护. 2.3数据库的基本维护 对Access定期检查,修复是整个数据库重要部分: 1.Access可修复 ...

  4. 记录使用 Cake 进行构建并制作 nuget 包

    书接上一回(https://www.cnblogs.com/h82258652/p/4898983.html)?[手动狗头] 前段时间折腾了一下,总算是把我自己的图片缓存控件(https://gith ...

  5. WITH RECOMPILE和OPTION(RECOMPILE)区别仅仅是存储过程级重编译和SQL语句级重编译吗

    在考虑重编译T-SQL(或者存储过程)的时候,有两种方式可以实现强制重编译(前提是忽略导致重编译的其他因素的情况下,比如重建索引,更新统计信息等等), 一是基于WITH RECOMPILE的存储过程级 ...

  6. vue keep-alive解决关闭标签动态缓存问题

    直接上代码: <keep-alive :include='topNavMentNames'> <router-view ></router-view> </k ...

  7. OSLab多线程

      日期:2019/3/26 内容:多线程. 一.基本知识 线程的定义 线程(thread)是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单 ...

  8. Java 实现网络图片的读取与下载

    //网络图片的下载,读取与删除 public static void fileDowAndDel(String httpurl){ try { URL url = new URL(httpurl); ...

  9. WCF绑定netTcpBinding寄宿到控制台应用程序

    契约 新建一个WCF服务类库项目,在其中添加两个WCF服务:GameService,PlayerService 代码如下: [ServiceContract] public interface IGa ...

  10. webpack打包工具

    目的:平时小项目中例如一些网站需要进行打包压缩,用这个工具可以进行打包压缩,就可以上传到服务器. 使用方法: 1,引进需要打包的项目,把入口html替换掉项目中的index.html,把引进的js,c ...