第十二章:定时任务

一、定时任务概述

定时任务是后端应用中非常常见的需求,如定时数据清理、报表生成、状态检查、消息推送等。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客户端