第十二章:定时任务
一、定时任务概述
定时任务是后端应用中非常常见的需求,如定时数据清理、报表生成、状态检查、消息推送等。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 提供的定时任务方案,可以轻松应对从简单的周期性任务到复杂的分布式调度场景。关键在于根据实际需求选择合适的方案,并遵循最佳实践来确保任务的可靠性和可维护性。