• [开源精品] .NET 定时任务 -- FreeScheduler 支持 cron、持久化、可变定时设置


    💻 前言

    卷了,卷了,卷了,最近太卷。。。这篇文章写了好几天了,由于同类型文章太多,排期到今天发布。实在不想卷,得罪了!各位定时任务开源大佬们!

    .NET 定时组件生态实在太强大了,写下此文只希望能供大家多一个选择,不想重复造轮子,实在是事出有因。

    高中读书那会,盛大传奇是最火爆的网络游戏,我和我的同学们都对它有过沉迷,甚至到上班几年之后,对它仍然有一种莫名的情怀。

    干我们这行忙的时候要加班,闲的时候也很闲,在我曾经很闲的一份工作里,为了情怀去研究了传奇sf引擎,在简洁的脚本代码里我发现了一个宝藏:活动定时任务。除了以秒单位定时触发,还可以设置每月某天、每周某天、每天某时间,在 .net framework 3.0 普遍还在使用 Timer 的年代,我一下子被惊艳到了,于是利用 Timer 仿着功能自己实现了一版 .NET 定时任务功能类。

    一开始只是一个类直接放进项目内使用,从未发布过 nuget 版本。打从 2016 年接触 .net core 以来,励志为开源生涯添砖加瓦,这才有了正式发布的念头。我曾经维护过 csredis(因原作者不维护所以发布为 CSRedisCore),呕心沥血从零到一开源 FreeSql,重构 RedisClient 发布的 FreeRedis,聊天架构 IMCore。。。等等。

    因 FreeSql 使用需求编写了有趣的开源组件 IdleBus,写完后发现它的特点还蛮适合用来扩展定时任务,于时重构了一个版本命名 IdleScheduler,在 2020 年发布开源,前不久已正式改名为 FreeScheduler。

    经历了十几年的使用需求和改造进化,实在是"食之无味,弃之可惜"。还是供大家多一个选择吧!


    ⛳ 主要优势

    FreeScheduler 轻量化定时任务调度,支持临时的延时任务和重复循环任务(可持久化),可按秒,每天/每周/每月固定时间,自定义间隔执行(CRON表达式),支持 .NET Framework 4.0,.NETCore2.1 +,Xamarin、MAUI 等平台 运行环境。

    特色功能之一:FreeScheduler 支持一个任务设置 [5,5,30,30,60] 不同的定时间隔,任何一次成功都可结束整个任务。

    scheduler.AddTask("比武大会", "json", new [] { 5, 5, 30, 30, 60 });
    
    class MyTaskHandler : FreeScheduler.TaskHandlers.TestHandler
    {
        public override void OnExecuting(Scheduler scheduler, TaskInfo task)
        {
            Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] {task.Topic} 被执行");
            if (task.Topic == "比武大会")
            {
                try
                {
                    //todo..
                    //任何一次不报错,强制使任务完成
                    task.Status = TaskStatus.Completed;
                }
                finaly
                {
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    轻量化解释:了解 FreeRedis、FreeSql、csredis 的人都知道,我们发布的开源项目是绿色著称,零依赖发布后只有一个DLL,不会造成使用者项目依赖冲突,支持 .NET 4.0 堪称屎山项目的救星。现在还有很多.NET FX4.0 的项目,这些项目因历史遗留原因或硬件限制,不能更换 .NET Core 版本。因此这些项目很难使用到现有的开源库,不能使用可靠的开源库,那么很多时候都要自行实现,在堆积代码的同时,项目也有可能越来越乱,代码越来越渣,项目逐渐变得不稳定。


    ⚡ 快速开始

    开源地址:https://github.com/2881099/FreeScheduler

    演示代码:https://github.com/2881099/FreeScheduler/blob/master/Examples/Examples_FreeScheduler_WinformNet40/Form1.cs

    dotnet add package FreeScheduler

    或者

    Install-Package FreeScheduler

    public static Scheduler scheduler = new Scheduler(new MyTaskHandler()); //单例模式,尽量保证只创建一次
    
    • 1

    📡 临时任务

    临时任务属于内存任务,不可持久化。

    void Callback()
    {
        Console.WriteLine("时间到了");
        scheduler.AddTempTask(TimeSpan.FromSeconds(10), Callback); //设置下一次定时
    }
    scheduler.AddTempTask(TimeSpan.FromSeconds(10), Callback);
    
    //如果是一次性任务,可以这样写:
    scheduler.AddTempTask(TimeSpan.FromSeconds(10), () =>
    {
        Console.WriteLine("时间到了");
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    Method说明
    string AddTempTask(TimeSpan, Action)创建临时的延时任务,返回 id
    bool RemoveTempTask(string id)删除任务(临时任务)
    bool ExistsTempTask(string id)判断任务是否存在(临时任务)
    int QuantityTempTask任务数量(临时任务)

    本地环境测试 50万 个临时任务,占用内存 383M,全部执行完成耗时 70秒。

    • Quartz.net 内存溢出,耗时 50秒
    • FluentScheduler 占用内存 1700M,耗时 未知
    • HashedWheelTimer 占用内存 213M,耗时 34秒

    我尝试过把 FreeScheduler 内核改成 HashedWheelTimer 内存占用更高(600兆),原因是 FreeScheduler 功能需要占用更多资源。


    🎣 循环任务

    • 临时任务是一次性触发,触发体是 Action 委托
    • 循环任务是周期性重复触发,触发体是 FreeScheduler.ITaskHandler,如上述 MyTestHandler
    Method说明
    void ctor(ITaskHandler)指定任务调度器(单例)
    string AddTask(string topic, string body, int round, int seconds)创建循环定时任务,返回 id
    string AddTask(string topic, string body, int[] seconds)创建每轮间隔不同的定时任务,返回 id
    string AddTaskRunOnDay(…)创建每日循环任务,指定utc时间,返回 id
    string AddTaskRunOnWeek(…)创建每周循环任务,指定utc时间,返回 id
    string AddTaskRunOnMonth(…)创建每月循环任务,指定utc时间,返回 id
    string AddTaskCustom(string topic, string body, string expression)创建自定义任务,返回 id
    bool RemoveTask(string id)删除任务
    bool ExistsTask(string id)判断任务是否存在
    bool ResumeTask(string id)恢复已暂停的任务
    bool PauseTask(string id)暂停正在运行的任务
    TaskInfo[] FindTask(lambda)查询正在运行中的任务
    int QuantityTask任务数量
    //每5秒触发,执行N次
    var id = scheduler.AddTask("topic1", "body1", round: -1, 5);
    
    //每次 不同的间隔秒数触发,执行6次
    var id = scheduler.AddTask("topic1", "body1", new [] { 5, 5, 10, 10, 60, 60 });
    
    //每天 20:00:00 触发,指定utc时间,执行N次
    var id = scheduler.AddTaskRunOnDay("topic1", "body1", round: -1, "20:00:00");
    
    //每周一 20:00:00 触发,指定utc时间,执行1次
    var id = scheduler.AddTaskRunOnWeek("topic1", "body1", round: 1, "1:20:00:00");
    
    //每月1日 20:00:00 触发,指定utc时间,执行12次
    var id = scheduler.AddTaskRunOnMonth("topic1", "body1", round: 12, "1:20:00:00");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    🌈 Cron

    由于 .NET Cron 组件普遍不支持年,因此 FreeScheduler 默认没有集成,但是很容易扩展实现,如下:

    var id = scheduler.AddTaskCustom("topic1", "body1", "0/1 * * * * ? ");
    
    public static Scheduler scheduler = new Scheduler(new MyTaskHandler(), new CronCustomHandler()); //单例模式,尽量保证只创建一次
    class CronCustomHandler : FreeScheduler.ITaskIntervalCustomHandler
    {
        public TimeSpan? NextDelay(TaskInfo task)
        {
            //利用 cron 功能库解析 task.IntervalArgument 得到下一次执行时间
            //与当前时间相减,得到 TimeSpan,若返回 null 则任务完成
            return TimeSpan.FromSeconds(5);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    🌳 持久化

    FreeScheduler 把任务分为两种类型,临时任务和循环任务,注意临时任务不支持持久化。

    当前已支持 数据库或Redis 持久化实现,各有优缺点:

    • 数据库,性能低,方便接入任务管理(后台管理系统)
    • Redis,性能高,由于分页的特点,接入任务管理功能略难

    使用持久化只需要把 Scheduler 构造参数修改,如下:

    var fsql = new FreeSql.FreeSqlBuilder()
        .UseConnectionString(FreeSql.DataType.Sqlite, "data source=task.db;max pool size=5")
        .UseAutoSyncStructure(true)
        .UseNoneCommandParameter(true)
        .UseMonitorCommand(cmd => Console.WriteLine($"=========sql: {cmd.CommandText}\r\n"))
        .Build();
    Scheduler scheduler = new Scheduler(new MyTaskHandler(fsql));
    
    class MyTaskHandler : FreeScheduler.TaskHandlers.FreeSqlHandler
    {
        public MyTaskHandler(IFreeSql fsql) : base(fsql) { }
    
        public override void OnExecuting(Scheduler scheduler, TaskInfo task)
        {
            Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] {task.Topic} 被执行");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Redis 持久化请安装:

    dotnet add package FreeScheduler.TaskHandlers.FreeRedis

    Install-Package FreeScheduler.TaskHandlers.FreeRedis


    📰 管理任务

    // 使用 FreeSql 或者 SQL 查询 TaskInfo、TaskLog 两个表进行分页显示
    fsql.Select<TaskInfo>().Count(out var total).Page(pageNumber, 30).ToList();
    fsql.Select<TaskLog>().Count(out var total).Page(pageNumber, 30).ToList();
    
    //暂停任务
    scheduler.PauseTask(id);
    //恢复暂停的任务
    scheduler.ResumeTask(id);
    //删除任务
    scheduler.RemoveTask(id);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    🌴 性能参考

    FreeSchedulerQuartz.netFluentSchedulerHashedWheelTimer
    (500,000 Tasks + 10s)(500,000 Tasks + 10s)(500,000 Tasks + 10s)(500,000 Tasks + 10s)
    383M1700+MStackOverflow213M
    70563.6066ms50692.5365ms未知33697.8758ms

    FluentScheduler 单个 Registry 测试正常,但目测单线程执行(间隔1-10ms),处理速度不理想 View Code

    我尝试把 FreeScheduler 内核改成 HashedWheelTimer 内存占用更高(600兆),结论:FreeScheduler 功能需要占用更多资源


    🌌 结束语

    .NET 定时任务组件太多了,以至于过去这些年都还没有正式推广过,希望能帮助到有需求的朋友。

    开源地址:https://github.com/2881099/FreeScheduler


    作者是什么人?

    作者是一个入行 18年的老批,他目前写的.net 开源项目有:

    开源项目描述开源地址开源协议
    ImCore聊天系统架构https://github.com/2881099/imMIT
    FreeRedisRedis SDKhttps://github.com/2881099/FreeRedisMIT
    csredishttps://github.com/2881099/csredisMIT
    FightLandlord斗DI主网络版https://github.com/2881099/FightLandlord学习用途
    FreeScheduler定时任务https://github.com/2881099/FreeSchedulerMIT
    IdleBus空闲容器https://github.com/2881099/IdleBusMIT
    FreeSqlORMhttps://github.com/dotnetcore/FreeSqlMIT
    FreeSql.Cloud分布式tcc/sagahttps://github.com/2881099/FreeSql.CloudMIT
    FreeSql.AdminLTE低代码后台生成https://github.com/2881099/FreeSql.AdminLTEMIT
    FreeSql.DynamicProxy动态代理https://github.com/2881099/FreeSql.DynamicProxy学习用途

    需要的请拿走,这些都是最近几年的开源作品,以前更早写的就不发了。

  • 相关阅读:
    调用ABC自带标准脚本文件
    每日一题:地下城游戏
    HTTP面试题总结
    生物信息学——文件格式 pileup详解
    【qsort学习及改造冒泡排序能排序任何数】
    RunLoop小白入门
    基于事件驱动的任务分布式调度消费方案
    数据包远程传输的抓包系统scratch
    【HCIP】RSTP
    Django笔记四十四之Nginx+uWSGI部署Django以及Nginx负载均衡操作
  • 原文地址:https://blog.csdn.net/dotnetCore/article/details/126519425