跳转到内容

任务调度

页面 /setting/job,实体 SysTask + SysTaskLog(按月分表)。它是框架 Tasks 调度引擎管理面:把任务定义落库、在页面上启停与手动触发,执行仍由框架的调度器完成。

数据模型

SysTask 的字段分四组:

标识与执行目标

字段说明
TaskCode / TaskName / TaskDescription编码、名称、描述
TaskGroup分组,便于按业务域归类
TaskClass执行类的类型名——调度器据此解析出 IJobWorker
TaskMethod方法名(按需)
TaskParams参数(JSON 字符串),执行时注入上下文

触发方式

字段说明默认
TriggerTypeImmediate / Cron / 间隔 等Immediate
CronExpressionCron 表达式(TriggerType 为 Cron 时用)
IntervalSeconds固定间隔秒数
StartTime / EndTime生效窗口
RepeatCount重复次数上限,-1 表示不限-1
NextRunTime / LastRunTime / ExecutedCount运行时游标

前端用 CronExpression.vue 组件(输入框 + 可视化弹窗)编辑 Cron,不必手写表达式。

执行控制

字段说明默认
TimeoutSeconds单次执行超时300
AllowConcurrent是否允许并发重入false
RetryCount / MaxRetryCount已重试 / 最大重试0 / 3
Priority优先级0
RunTaskStatus运行状态(Pending / Running / …)Pending
Status启用状态Enabled

启动时同步

Saas 模块在 OnPostApplicationInitialization所有模块都就绪之后)做两件事:

  1. 扫描代码里声明式定义的任务;
  2. 把数据库中活跃的 SysTask 同步进调度器。

同时复位崩溃残留的 Running 状态——进程被杀时正在跑的任务会永远停在 Running,既不会被再次触发(AllowConcurrent=false 时会被判为「已在运行」而跳过),也不会自己结束。不复位就等于这个任务从此再也不执行了。

新加的任务没跑起来

按顺序查:

  1. Status 是不是 Enabled
  2. TaskClass 能不能解析到实际类型(改过命名空间/类名?);
  3. Cron 表达式算不算得出下次时间——算不出时任务会「注册了但永不执行」,框架会打 Warning 日志,去日志里搜;
  4. EndTime 是不是已经过了、RepeatCount 是不是已经用完;
  5. RunTaskStatus 是不是卡在 Running(崩溃残留没复位)。

并发与多实例

AllowConcurrent = false 依赖任务锁防止重入。跨实例防并发需要 Redis——框架的锁来自 Caching,未接 Redis 时退化为进程内锁,多实例部署会各跑各的。

生产多实例部署时这一点务必确认,否则会出现「日报表发了三份」这类问题。

多租户

任务执行时会切换到正确的租户上下文(ICurrentTenant.Change),解析优先级是:参数里的 tenantIdSysTask 的租户归属 → 当前异步上下文租户。宿主级任务的租户为空。

这意味着同一个任务类可以被不同租户各配一份,各自跑在自己的数据范围里。

执行日志

SysTaskLogSys_Task_Log_{yyyyMM},按月分表)记录每次执行的开始/结束、耗时、结果与异常。分表数据要走 SqlSugar 的分表 API 按时间范围查,不能当普通表直接查。

与其它异步机制的分工

需求用什么
周期性/定时触发(每天凌晨跑报表)本页的任务调度 + SysTask
一次性 fire-and-forget,入队即返回框架 IBackgroundJobManager.EnqueueAsync
持续拉队列消费、业务表做状态机XiHanBackgroundServiceBase<T> + IRedisDelayQueue<T>(发件箱、导出走这条)
等人审批的长流程工作流

相关页面

Released under The MIT License