惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Jina AI
Jina AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Troy Hunt's Blog
T
The Exploit Database - CXSecurity.com
Microsoft Security Blog
Microsoft Security Blog
V
Visual Studio Blog
F
Fortinet All Blogs
博客园_首页
P
Proofpoint News Feed
V
Vulnerabilities – Threatpost
The Cloudflare Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
H
Heimdal Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
A
About on SuperTechFans
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
AI
AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
S
Security Affairs
The Register - Security
The Register - Security
S
Security @ Cisco Blogs
Hugging Face - Blog
Hugging Face - Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 聂微东
Schneier on Security
Schneier on Security
WordPress大学
WordPress大学
Google DeepMind News
Google DeepMind News
GbyAI
GbyAI
T
Tailwind CSS Blog
Hacker News: Ask HN
Hacker News: Ask HN
W
WeLiveSecurity
D
Docker
L
LangChain Blog
B
Blog RSS Feed
The Last Watchdog
The Last Watchdog
Cloudbric
Cloudbric
TaoSecurity Blog
TaoSecurity Blog
N
Netflix TechBlog - Medium
酷 壳 – CoolShell
酷 壳 – CoolShell
I
InfoQ
The Hacker News
The Hacker News
AWS News Blog
AWS News Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
宝玉的分享
宝玉的分享
I
Intezer
云风的 BLOG
云风的 BLOG
V2EX - 技术
V2EX - 技术
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第十二章:定时任务
我才是银古 · 2026-06-21 · via 博客园 - 我才是银古

第十二章:定时任务

一、定时任务概述

定时任务是后端应用中非常常见的需求,如定时数据清理、报表生成、状态检查、消息推送等。Furion 框架提供了多种定时任务实现方式,包括 SpareTime 静态类、IJob 接口、以及新版 Schedule 模块,满足从简单到复杂的各种调度需求。

1.1 定时任务应用场景

场景 说明 频率
数据库备份 定期备份数据库 每天凌晨
缓存刷新 定期更新缓存数据 每隔几分钟
订单超时处理 取消超时未支付的订单 每分钟检查
报表生成 生成日/周/月报表 按时间周期
日志清理 清理过期日志文件 每周/每月
健康检查 检测外部服务可用性 每隔几秒
消息推送 定时推送消息通知 定时触发
数据同步 与第三方系统同步数据 每隔一段时间

1.2 Furion 定时任务方案对比

方案 适用场景 复杂度 特点
SpareTime 简单定时任务 静态方法调用,快速上手
IJob 接口 中等复杂度任务 面向接口,便于管理
Schedule 模块 复杂调度需求 中高 功能完善,支持持久化
BackgroundService .NET 原生方案 适合长期运行的后台服务

二、SpareTime 静态类

2.1 基本用法

SpareTime 是 Furion 提供的轻量级定时任务静态类,适合快速创建简单任务:

// 间隔执行(每5秒执行一次)
SpareTime.Do(5000, (timer, count) =>
{
    Console.WriteLine($"第 {count} 次执行,时间:{DateTime.Now}");
}, "MyTimer");

// 只执行一次
SpareTime.DoOnce(3000, (timer, count) =>
{
    Console.WriteLine("延迟3秒后执行一次");
}, "OnceTimer");

// Cron 表达式
SpareTime.Do("*/5 * * * *", (timer, count) =>
{
    Console.WriteLine("每5分钟执行一次");
}, "CronTimer");

2.2 任务暂停与恢复

// 创建任务
SpareTime.Do(5000, (timer, count) =>
{
    Console.WriteLine($"正在执行任务...");
}, "task1");

// 暂停任务
SpareTime.Stop("task1");

// 恢复任务
SpareTime.Start("task1");

// 销毁任务
SpareTime.Cancel("task1");

// 获取所有任务
var allTimers = SpareTime.GetTimers();
foreach (var timer in allTimers)
{
    Console.WriteLine($"任务:{timer.WorkerName},状态:{timer.Status}");
}

2.3 在控制器中使用

[ApiDescriptionSettings("定时任务管理")]
public class TimerAppService : IDynamicApiController
{
    /// <summary>
    /// 启动数据同步任务
    /// </summary>
    public void StartSyncTask()
    {
        SpareTime.Do(60000, async (timer, count) =>
        {
            await SyncDataFromExternalApi();
        }, "DataSyncTimer", description: "数据同步定时任务");
    }

    /// <summary>
    /// 停止数据同步任务
    /// </summary>
    public void StopSyncTask()
    {
        SpareTime.Stop("DataSyncTimer");
    }

    /// <summary>
    /// 获取所有任务状态
    /// </summary>
    public object GetAllTimerStatus()
    {
        return SpareTime.GetTimers().Select(t => new
        {
            t.WorkerName,
            t.Description,
            Status = t.Status.ToString(),
            t.Tally
        });
    }

    private Task SyncDataFromExternalApi() => Task.CompletedTask;
}

三、IJob 接口

3.1 定义 Job

通过实现 IJob 接口定义定时任务:

/// <summary>
/// 订单超时检查任务
/// </summary>
[JobDetail("OrderTimeoutJob", Description = "检查并取消超时未支付订单")]
[Cron("0 */1 * * * ?", TriggerId = "OrderTimeoutTrigger", Description = "每分钟执行")]
public class OrderTimeoutJob : IJob
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<OrderTimeoutJob> _logger;

    public OrderTimeoutJob(
        IServiceScopeFactory scopeFactory,
        ILogger<OrderTimeoutJob> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
    {
        _logger.LogInformation("开始检查超时订单...");

        using var scope = _scopeFactory.CreateScope();
        var orderRepository = scope.ServiceProvider.GetRequiredService<IRepository<Order>>();

        var timeoutThreshold = DateTime.Now.AddMinutes(-30);
        var timeoutOrders = await orderRepository
            .Where(o => o.Status == OrderStatus.Pending && o.CreatedTime < timeoutThreshold)
            .ToListAsync(stoppingToken);

        foreach (var order in timeoutOrders)
        {
            order.Status = OrderStatus.Cancelled;
            order.CancelReason = "超时未支付,系统自动取消";
            _logger.LogInformation("订单 {OrderId} 已自动取消", order.Id);
        }

        await orderRepository.SaveNowAsync(stoppingToken);
        _logger.LogInformation("超时订单检查完成,共取消 {Count} 个订单", timeoutOrders.Count);
    }
}

3.2 多触发器任务

一个 Job 可以配置多个触发器:

/// <summary>
/// 报表生成任务
/// </summary>
[JobDetail("ReportJob", Description = "报表生成")]
[Cron("0 0 2 * * ?", TriggerId = "DailyReportTrigger", Description = "每天凌晨2点生成日报")]
[Cron("0 0 3 ? * MON", TriggerId = "WeeklyReportTrigger", Description = "每周一凌晨3点生成周报")]
[Cron("0 0 4 1 * ?", TriggerId = "MonthlyReportTrigger", Description = "每月1日凌晨4点生成月报")]
public class ReportJob : IJob
{
    private readonly ILogger<ReportJob> _logger;

    public ReportJob(ILogger<ReportJob> logger) => _logger = logger;

    public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
    {
        var triggerId = context.TriggerId;

        switch (triggerId)
        {
            case "DailyReportTrigger":
                await GenerateDailyReport();
                break;
            case "WeeklyReportTrigger":
                await GenerateWeeklyReport();
                break;
            case "MonthlyReportTrigger":
                await GenerateMonthlyReport();
                break;
        }
    }

    private async Task GenerateDailyReport()
    {
        _logger.LogInformation("生成日报...");
        await Task.CompletedTask;
    }

    private async Task GenerateWeeklyReport()
    {
        _logger.LogInformation("生成周报...");
        await Task.CompletedTask;
    }

    private async Task GenerateMonthlyReport()
    {
        _logger.LogInformation("生成月报...");
        await Task.CompletedTask;
    }
}

四、Cron 表达式详解

4.1 Cron 表达式格式

Furion 支持标准的 6 位 Cron 表达式(含秒)和 7 位表达式(含年):

秒 分 时 日 月 周 [年]
*  *  *  *  *  *  [*]

4.2 字段说明

字段 允许值 允许特殊字符
0-59 , - * /
0-59 , - * /
0-23 , - * /
1-31 , - * / ? L W
1-12 或 JAN-DEC , - * /
0-6 或 SUN-SAT , - * / ? L #
年(可选) 1970-2099 , - * /

4.3 常用 Cron 表达式

表达式 含义
* * * * * ? 每秒执行
0 * * * * ? 每分钟执行
0 */5 * * * ? 每5分钟执行
0 0 * * * ? 每小时执行
0 0 2 * * ? 每天凌晨2点执行
0 0 2 1 * ? 每月1日凌晨2点执行
0 0 10,14,16 * * ? 每天10点、14点、16点执行
0 0/30 9-17 * * ? 每天9-17点每半小时执行
0 0 2 ? * MON-FRI 工作日凌晨2点执行
0 0 2 L * ? 每月最后一天凌晨2点执行
0 0 2 ? * 6#3 每月第三个周五凌晨2点执行

4.4 特殊字符说明

字符 含义 示例
* 所有值 每秒/每分/每时
? 不指定值 用于日和周字段
- 范围 10-12 表示10到12
, 列表 MON,WED,FRI
/ 步长 0/15 从0开始每15
L 最后 月最后一天或周最后一天
W 工作日 15W 最近15号的工作日
# 第几个 6#3 第三个周五

五、任务注册与管理

5.1 在 Program.cs 中注册

var builder = WebApplication.CreateBuilder(args);

// 注册 Schedule 模块
builder.Services.AddSchedule(options =>
{
    // 自动扫描并注册所有 IJob 实现
    options.AddJob<OrderTimeoutJob>();
    options.AddJob<ReportJob>();
    options.AddJob<DataCleanupJob>();
});

var app = builder.Build();
app.Run();

5.2 动态管理任务

[ApiDescriptionSettings("任务管理")]
public class ScheduleAppService : IDynamicApiController
{
    private readonly ISchedulerFactory _schedulerFactory;

    public ScheduleAppService(ISchedulerFactory schedulerFactory)
    {
        _schedulerFactory = schedulerFactory;
    }

    /// <summary>
    /// 添加新任务
    /// </summary>
    public void AddJob(AddJobInput input)
    {
        _schedulerFactory.AddJob(
            JobBuilder.Create<DynamicJob>()
                .SetJobId(input.JobId)
                .SetDescription(input.Description),
            TriggerBuilder.Create(input.CronExpression)
                .SetTriggerId(input.TriggerId)
                .SetDescription(input.TriggerDescription)
        );
    }

    /// <summary>
    /// 暂停任务
    /// </summary>
    public void PauseJob(string jobId)
    {
        var scheduler = _schedulerFactory.GetJob(jobId);
        scheduler?.Pause();
    }

    /// <summary>
    /// 恢复任务
    /// </summary>
    public void ResumeJob(string jobId)
    {
        var scheduler = _schedulerFactory.GetJob(jobId);
        scheduler?.Start();
    }

    /// <summary>
    /// 移除任务
    /// </summary>
    public void RemoveJob(string jobId)
    {
        _schedulerFactory.RemoveJob(jobId);
    }

    /// <summary>
    /// 获取所有任务信息
    /// </summary>
    public object GetAllJobs()
    {
        return _schedulerFactory.GetJobs().Select(j => new
        {
            j.JobId,
            j.Description,
            Status = j.GetStatus().ToString(),
            Triggers = j.GetTriggers().Select(t => new
            {
                t.TriggerId,
                t.Description,
                t.NextRunTime,
                t.LastRunTime,
                Status = t.Status.ToString()
            })
        });
    }

    /// <summary>
    /// 立即触发任务
    /// </summary>
    public void TriggerJob(string jobId)
    {
        var scheduler = _schedulerFactory.GetJob(jobId);
        scheduler?.Run();
    }
}

六、任务监控

6.1 任务执行日志

/// <summary>
/// 任务监控器
/// </summary>
public class JobMonitor : IJobMonitor
{
    private readonly ILogger<JobMonitor> _logger;

    public JobMonitor(ILogger<JobMonitor> logger) => _logger = logger;

    /// <summary>
    /// 任务执行前
    /// </summary>
    public Task OnExecutingAsync(JobExecutingContext context, CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "任务 [{JobId}] 开始执行,触发器:{TriggerId},执行时间:{Time}",
            context.JobId, context.TriggerId, DateTime.Now);
        return Task.CompletedTask;
    }

    /// <summary>
    /// 任务执行后
    /// </summary>
    public Task OnExecutedAsync(JobExecutedContext context, CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "任务 [{JobId}] 执行完成,耗时:{Elapsed}ms,结果:{Result}",
            context.JobId, context.Elapsed.TotalMilliseconds, context.Result);
        return Task.CompletedTask;
    }
}

6.2 注册监控器

builder.Services.AddSchedule(options =>
{
    options.AddJob<OrderTimeoutJob>();

    // 注册任务监控器
    options.AddMonitor<JobMonitor>();
});

6.3 任务执行器(自定义执行逻辑)

/// <summary>
/// 自定义任务执行器
/// </summary>
public class CustomJobExecutor : IJobExecutor
{
    private readonly ILogger<CustomJobExecutor> _logger;

    public CustomJobExecutor(ILogger<CustomJobExecutor> logger) => _logger = logger;

    public async Task ExecuteAsync(JobExecutingContext context, IJob jobHandler, CancellationToken stoppingToken)
    {
        try
        {
            // 执行前记录
            var stopwatch = System.Diagnostics.Stopwatch.StartNew();

            // 执行任务
            await jobHandler.ExecuteAsync(context, stoppingToken);

            stopwatch.Stop();
            _logger.LogInformation("任务 {JobId} 执行成功,耗时 {Elapsed}ms",
                context.JobId, stopwatch.ElapsedMilliseconds);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "任务 {JobId} 执行失败:{Message}", context.JobId, ex.Message);

            // 可以在此处发送告警通知
            await SendAlertNotification(context.JobId, ex.Message);
        }
    }

    private Task SendAlertNotification(string jobId, string error) => Task.CompletedTask;
}

七、任务异常处理

7.1 异常捕获与处理

[JobDetail("ResilientJob", Description = "带异常处理的任务")]
[Cron("0 */5 * * * ?", TriggerId = "ResilientTrigger")]
public class ResilientJob : IJob
{
    private readonly ILogger<ResilientJob> _logger;

    public ResilientJob(ILogger<ResilientJob> logger) => _logger = logger;

    public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
    {
        try
        {
            await DoWork(stoppingToken);
        }
        catch (OperationCanceledException)
        {
            _logger.LogWarning("任务被取消");
        }
        catch (TimeoutException ex)
        {
            _logger.LogError(ex, "任务执行超时");
            // 记录失败并等待下次重试
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "任务执行异常");

            // 根据异常类型决定是否需要人工介入
            if (ex is CriticalException)
            {
                await NotifyAdministrator(ex);
            }
        }
    }

    private async Task DoWork(CancellationToken stoppingToken)
    {
        // 实际业务逻辑
        await Task.Delay(1000, stoppingToken);
    }

    private Task NotifyAdministrator(Exception ex) => Task.CompletedTask;
}

7.2 重试策略

[JobDetail("RetryJob", Description = "带重试的任务")]
[Cron("0 */10 * * * ?", TriggerId = "RetryTrigger")]
public class RetryJob : IJob
{
    private readonly ILogger<RetryJob> _logger;
    private const int MaxRetries = 3;

    public RetryJob(ILogger<RetryJob> logger) => _logger = logger;

    public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
    {
        var retryCount = 0;

        while (retryCount < MaxRetries)
        {
            try
            {
                await ExecuteCore(stoppingToken);
                _logger.LogInformation("任务执行成功");
                return;
            }
            catch (Exception ex) when (retryCount < MaxRetries - 1)
            {
                retryCount++;
                var delay = (int)Math.Pow(2, retryCount) * 1000; // 指数退避
                _logger.LogWarning(ex, "任务执行失败,{RetryCount}/{MaxRetries} 次重试,等待 {Delay}ms",
                    retryCount, MaxRetries, delay);
                await Task.Delay(delay, stoppingToken);
            }
        }
    }

    private Task ExecuteCore(CancellationToken stoppingToken) => Task.CompletedTask;
}

八、Schedule 模块(新版)

8.1 Schedule 模块概述

Furion 新版 Schedule 模块提供了更强大、更灵活的任务调度能力:

builder.Services.AddSchedule(options =>
{
    // 构建器方式创建任务
    options.AddJob(JobBuilder.Create<DataSyncJob>()
        .SetJobId("data-sync")
        .SetDescription("数据同步任务")
        .SetConcurrent(false), // 不允许并发执行
        TriggerBuilder.Create("*/30 * * * * ?")
            .SetTriggerId("data-sync-trigger")
            .SetDescription("每30秒执行")
            .SetMaxNumberOfRuns(100) // 最多执行100次
            .SetStartNow(true) // 立即开始
    );
});

8.2 任务构建器

// 使用 JobBuilder 灵活配置任务
var jobBuilder = JobBuilder.Create<CleanupJob>()
    .SetJobId("cleanup-job")
    .SetDescription("清理临时数据")
    .SetGroupName("maintenance")
    .SetConcurrent(false);

// 使用 TriggerBuilder 配置触发器
var triggerBuilder = TriggerBuilder.Create("0 0 3 * * ?")
    .SetTriggerId("cleanup-trigger")
    .SetDescription("每天凌晨3点执行")
    .SetRunOnStart(false)
    .SetStartTime(DateTime.Now.AddHours(1)) // 1小时后开始
    .SetEndTime(DateTime.Now.AddMonths(6)); // 6个月后结束

8.3 动态创建任务

/// <summary>
/// 动态任务服务
/// </summary>
public class DynamicJobService
{
    private readonly ISchedulerFactory _factory;

    public DynamicJobService(ISchedulerFactory factory) => _factory = factory;

    /// <summary>
    /// 创建一次性任务
    /// </summary>
    public void CreateOneTimeJob(string jobId, DateTime executeTime)
    {
        _factory.AddJob(
            JobBuilder.Create<DynamicJob>()
                .SetJobId(jobId)
                .SetDescription("一次性任务"),
            TriggerBuilder.Create()
                .SetTriggerId($"{jobId}-trigger")
                .SetStartTime(executeTime)
                .SetMaxNumberOfRuns(1)
        );
    }

    /// <summary>
    /// 更新任务触发器
    /// </summary>
    public void UpdateJobTrigger(string jobId, string triggerId, string newCron)
    {
        var scheduler = _factory.GetJob(jobId);
        scheduler?.UpdateTrigger(triggerId, trigger =>
        {
            trigger.SetCron(newCron);
        });
    }
}

九、后台任务(BackgroundService)

9.1 使用 .NET 原生 BackgroundService

/// <summary>
/// 消息队列消费后台服务
/// </summary>
public class MessageConsumerService : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<MessageConsumerService> _logger;

    public MessageConsumerService(
        IServiceScopeFactory scopeFactory,
        ILogger<MessageConsumerService> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("消息消费服务已启动");

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                using var scope = _scopeFactory.CreateScope();
                var messageService = scope.ServiceProvider.GetRequiredService<IMessageService>();

                var messages = await messageService.GetPendingMessagesAsync(stoppingToken);
                foreach (var message in messages)
                {
                    await messageService.ProcessMessageAsync(message, stoppingToken);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "消息处理异常");
            }

            await Task.Delay(5000, stoppingToken);
        }

        _logger.LogInformation("消息消费服务已停止");
    }
}

9.2 注册后台服务

builder.Services.AddHostedService<MessageConsumerService>();

9.3 带健康检查的后台服务

/// <summary>
/// 带健康检查的后台任务
/// </summary>
public class HealthCheckBackgroundService : BackgroundService
{
    private readonly ILogger<HealthCheckBackgroundService> _logger;
    private readonly IHttpClientFactory _httpClientFactory;
    private DateTime _lastHealthyTime = DateTime.Now;

    public HealthCheckBackgroundService(
        ILogger<HealthCheckBackgroundService> logger,
        IHttpClientFactory httpClientFactory)
    {
        _logger = logger;
        _httpClientFactory = httpClientFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var endpoints = new[]
            {
                "https://api.service1.com/health",
                "https://api.service2.com/health",
                "https://api.service3.com/health"
            };

            foreach (var endpoint in endpoints)
            {
                try
                {
                    var client = _httpClientFactory.CreateClient();
                    client.Timeout = TimeSpan.FromSeconds(10);
                    var response = await client.GetAsync(endpoint, stoppingToken);

                    if (response.IsSuccessStatusCode)
                    {
                        _logger.LogDebug("{Endpoint} 健康检查通过", endpoint);
                        _lastHealthyTime = DateTime.Now;
                    }
                    else
                    {
                        _logger.LogWarning("{Endpoint} 健康检查失败,状态码:{StatusCode}",
                            endpoint, response.StatusCode);
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "{Endpoint} 健康检查异常", endpoint);
                }
            }

            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
        }
    }
}

十、分布式任务调度

10.1 分布式锁防止重复执行

在多实例部署时,需要使用分布式锁防止任务重复执行:

[JobDetail("DistributedJob", Description = "分布式安全任务")]
[Cron("0 */5 * * * ?", TriggerId = "DistributedTrigger")]
public class DistributedJob : IJob
{
    private readonly IDistributedLock _distributedLock;
    private readonly ILogger<DistributedJob> _logger;

    public DistributedJob(
        IDistributedLock distributedLock,
        ILogger<DistributedJob> logger)
    {
        _distributedLock = distributedLock;
        _logger = logger;
    }

    public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
    {
        var lockKey = $"job:lock:{context.JobId}";

        // 尝试获取分布式锁
        await using var lockHandle = await _distributedLock.TryAcquireAsync(lockKey, TimeSpan.FromMinutes(5));
        if (lockHandle == null)
        {
            _logger.LogInformation("任务 {JobId} 已在其他实例执行中,跳过", context.JobId);
            return;
        }

        _logger.LogInformation("获取锁成功,开始执行任务 {JobId}", context.JobId);
        await DoDistributedWork(stoppingToken);
    }

    private Task DoDistributedWork(CancellationToken stoppingToken) => Task.CompletedTask;
}

10.2 任务持久化

/// <summary>
/// 基于数据库的任务持久化器
/// </summary>
public class DbJobPersistence : IJobPersistence
{
    private readonly IServiceScopeFactory _scopeFactory;

    public DbJobPersistence(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory;

    /// <summary>
    /// 加载持久化的任务
    /// </summary>
    public IEnumerable<SchedulerBuilder> Preload()
    {
        using var scope = _scopeFactory.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var jobs = dbContext.ScheduledJobs
            .Where(j => j.IsEnabled)
            .ToList();

        return jobs.Select(j => SchedulerBuilder.Create(
            JobBuilder.Create(j.JobType)
                .SetJobId(j.JobId)
                .SetDescription(j.Description),
            TriggerBuilder.Create(j.CronExpression)
                .SetTriggerId(j.TriggerId)
        ));
    }

    /// <summary>
    /// 持久化任务变更
    /// </summary>
    public void OnChanged(PersistenceContext context)
    {
        using var scope = _scopeFactory.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        switch (context.Behavior)
        {
            case PersistenceBehavior.Appended:
                dbContext.ScheduledJobs.Add(new ScheduledJobEntity
                {
                    JobId = context.JobId,
                    CronExpression = context.Trigger?.Cron,
                    IsEnabled = true
                });
                break;

            case PersistenceBehavior.Removed:
                var job = dbContext.ScheduledJobs.Find(context.JobId);
                if (job != null)
                    dbContext.ScheduledJobs.Remove(job);
                break;

            case PersistenceBehavior.Updated:
                var existing = dbContext.ScheduledJobs.Find(context.JobId);
                if (existing != null)
                    existing.CronExpression = context.Trigger?.Cron;
                break;
        }

        dbContext.SaveChanges();
    }
}

十一、定时任务最佳实践

11.1 任务设计原则

/// <summary>
/// 最佳实践示例:幂等且可恢复的任务
/// </summary>
[JobDetail("BestPracticeJob", Description = "最佳实践任务示例")]
[Cron("0 */10 * * * ?", TriggerId = "BestPracticeTrigger")]
public class BestPracticeJob : IJob
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<BestPracticeJob> _logger;

    public BestPracticeJob(IServiceScopeFactory scopeFactory, ILogger<BestPracticeJob> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
    {
        // 1. 使用独立作用域
        using var scope = _scopeFactory.CreateScope();

        // 2. 幂等处理 - 检查是否已处理
        var processedIds = new HashSet<long>();

        // 3. 批量处理,控制单次处理量
        const int batchSize = 100;
        var items = await GetPendingItems(scope, batchSize, stoppingToken);

        // 4. 逐条处理,记录进度
        foreach (var item in items)
        {
            if (stoppingToken.IsCancellationRequested) break;

            try
            {
                await ProcessItem(scope, item);
                processedIds.Add(item.Id);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "处理项 {ItemId} 失败", item.Id);
                // 单条失败不影响其他项
            }
        }

        _logger.LogInformation("任务完成,成功处理 {Count}/{Total} 项",
            processedIds.Count, items.Count);
    }

    private Task<List<TaskItem>> GetPendingItems(IServiceScope scope, int batchSize, CancellationToken ct)
        => Task.FromResult(new List<TaskItem>());

    private Task ProcessItem(IServiceScope scope, TaskItem item) => Task.CompletedTask;
}

public class TaskItem
{
    public long Id { get; set; }
    public string Data { get; set; }
}

11.2 注意事项

要点 说明
幂等性 任务应设计为可重复执行而不产生副作用
超时控制 设置合理的超时时间,避免任务阻塞
异常处理 捕获异常并记录日志,避免任务静默失败
批量处理 控制单次处理数据量,避免内存溢出
作用域管理 使用 IServiceScopeFactory 创建独立作用域
并发控制 多实例部署时使用分布式锁
监控告警 配置任务监控和异常告警
日志记录 记录任务开始、结束、耗时和结果

通过合理选择和使用 Furion 提供的定时任务方案,可以轻松应对从简单的周期性任务到复杂的分布式调度场景。关键在于根据实际需求选择合适的方案,并遵循最佳实践来确保任务的可靠性和可维护性。


上一章: 第11章-事件总线 | 返回目录 | 下一章: 第13章-远程请求与HTTP客户端