# 定时任务暂停指南 ## 概述 项目中的PDF标签缓存定时任务是通过 `LabelPdfCacheBackgroundService` 实现的,它是一个 ASP.NET Core `BackgroundService`,每5分钟执行一次。 --- ## 定时任务信息 ### 基本参数 - **服务类**: `LabelPdfCacheBackgroundService` - **执行间隔**: 5分钟 - **功能**: 处理待处理的PDF缓存任务 - **执行方法**: `ProcessPendingTasksAsync()` ### 当前工作流程 1. 每5分钟检查一次待处理任务 2. 获取失效的缓存、未处理的任务、新订单 3. 批量处理这些任务 4. 同步保存PDF缓存,异步识别条码 --- ## 暂停定时任务的方法 ### 方法1:修改配置文件(推荐用于生产环境) #### 步骤1:修改 appsettings.json 在 `appsettings.json` 或 `appsettings.Production.json` 中添加一个配置开关: ```json { "BackgroundServices": { "LabelPdfCacheServiceEnabled": false } } ``` #### 步骤2:修改 Program.cs 修改Program.cs中的注册代码: ```csharp // 从这样: builder.Services.AddHostedService(); // 改为: var enableLabelPdfCache = builder.Configuration.GetValue("BackgroundServices:LabelPdfCacheServiceEnabled", true); if (enableLabelPdfCache) { builder.Services.AddHostedService(); } ``` #### 优点 - 无需重新编译代码 - 支持配置热更新 - 适合生产环境 #### 缺点 - 需要修改两个文件 - 需要重启应用 --- ### 方法2:使用环境变量 #### 步骤1:修改 Program.cs ```csharp var enableLabelPdfCache = !string.Equals( Environment.GetEnvironmentVariable("DISABLE_LABEL_PDF_CACHE"), "true", StringComparison.OrdinalIgnoreCase); if (enableLabelPdfCache) { builder.Services.AddHostedService(); } ``` #### 步骤2:设置环境变量 **Windows命令行:** ```bash set DISABLE_LABEL_PDF_CACHE=true ``` **Linux/Mac:** ```bash export DISABLE_LABEL_PDF_CACHE=true ``` **Docker:** ```dockerfile ENV DISABLE_LABEL_PDF_CACHE=true ``` #### 优点 - 不需要修改配置文件 - 容易在Docker容器中配置 - 支持运行时切换 #### 缺点 - 需要修改代码 - 需要重启应用 --- ### 方法3:创建暂停/恢复接口(最灵活) #### 步骤1:创建定时任务管理类 创建文件:`BackgroundServices/BackgroundServiceManager.cs` ```csharp using System.Threading; namespace CONTROLLER.BackgroundServices { /// /// 后台服务管理器 - 用于控制后台服务的启停 /// public class BackgroundServiceManager { private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); /// /// 获取取消令牌 /// public static CancellationToken GetCancellationToken() { return _cancellationTokenSource.Token; } /// /// 暂停服务 /// public static void PauseService() { if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested) { _cancellationTokenSource.Cancel(); } } /// /// 恢复服务 /// public static void ResumeService() { if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested) { _cancellationTokenSource = new CancellationTokenSource(); } } /// /// 获取服务状态 /// public static bool IsRunning() { return _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested; } } } ``` #### 步骤2:修改 LabelPdfCacheBackgroundService ```csharp using BLL.Interfaces; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Threading; using System.Threading.Tasks; namespace CONTROLLER.BackgroundServices { public class LabelPdfCacheBackgroundService : BackgroundService { private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private const int TaskIntervalMinutes = 5; public LabelPdfCacheBackgroundService( IServiceProvider serviceProvider, ILogger logger) { _serviceProvider = serviceProvider; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Label PDF Cache Background Service is starting"); while (!stoppingToken.IsCancellationRequested) { try { // 检查服务是否被暂停 if (!BackgroundServiceManager.IsRunning()) { _logger.LogInformation("Service is paused, waiting for resume..."); await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); continue; } _logger.LogInformation("Starting label PDF cache processing task"); using var scope = _serviceProvider.CreateScope(); var cacheService = scope.ServiceProvider.GetRequiredService(); var successCount = await cacheService.ProcessPendingTasksAsync(); _logger.LogInformation("Completed label PDF cache processing task, success count: {count}", successCount); } catch (Exception ex) { _logger.LogError(ex, "Error occurred in label PDF cache background service"); } await Task.Delay(TimeSpan.FromMinutes(TaskIntervalMinutes), stoppingToken); } _logger.LogInformation("Label PDF Cache Background Service is stopping"); } } } ``` #### 步骤3:在 LabelController 中添加管理接口 ```csharp /// /// 暂停PDF缓存定时任务 /// [HttpPost("background-service/pause")] public IActionResult PauseBackgroundService() { try { BackgroundServiceManager.PauseService(); _logger.LogInformation("Background service paused"); return Ok(new { status = "success", message = "后台定时任务已暂停", data = new { isRunning = false } }); } catch (Exception ex) { _logger.LogError(ex, "Error pausing background service"); return Ok(new { status = "error", message = "暂停定时任务失败", errorDetails = ex.Message }); } } /// /// 恢复PDF缓存定时任务 /// [HttpPost("background-service/resume")] public IActionResult ResumeBackgroundService() { try { BackgroundServiceManager.ResumeService(); _logger.LogInformation("Background service resumed"); return Ok(new { status = "success", message = "后台定时任务已恢复", data = new { isRunning = true } }); } catch (Exception ex) { _logger.LogError(ex, "Error resuming background service"); return Ok(new { status = "error", message = "恢复定时任务失败", errorDetails = ex.Message }); } } /// /// 获取后台定时任务状态 /// [HttpGet("background-service/status")] public IActionResult GetBackgroundServiceStatus() { try { var isRunning = BackgroundServiceManager.IsRunning(); return Ok(new { status = "success", message = "获取后台服务状态成功", data = new { isRunning = isRunning, service = "LabelPdfCacheBackgroundService", interval = "5 minutes" } }); } catch (Exception ex) { _logger.LogError(ex, "Error getting background service status"); return Ok(new { status = "error", message = "获取状态失败", errorDetails = ex.Message }); } } ``` #### 优点 - 可以在运行时动态控制 - 无需重启应用 - 最灵活,适合生产环境 #### 缺点 - 需要修改更多代码 - 状态只在内存中保存,重启后会重置 --- ## 方法对比 | 方法 | 修改代码 | 重启应用 | 实时性 | 推荐场景 | |------|--------|--------|-------|---------| | 方法1:配置文件 | 中等 | 是 | 低 | 生产环境固定配置 | | 方法2:环境变量 | 中等 | 是 | 低 | Docker容器部署 | | 方法3:管理接口 | 高 | 否 | 高 | 需要灵活控制 | --- ## 使用方法3的API调用示例 ### JavaScript/Fetch ```javascript // 暂停定时任务 async function pauseBackgroundService() { const response = await fetch('http://localhost:5002/api/label/background-service/pause', { method: 'POST', headers: { 'Content-Type': 'application/json', } }); const data = await response.json(); console.log(data); } // 恢复定时任务 async function resumeBackgroundService() { const response = await fetch('http://localhost:5002/api/label/background-service/resume', { method: 'POST', headers: { 'Content-Type': 'application/json', } }); const data = await response.json(); console.log(data); } // 获取定时任务状态 async function getBackgroundServiceStatus() { const response = await fetch('http://localhost:5002/api/label/background-service/status'); const data = await response.json(); console.log(data); } ``` ### Python ```python import requests BASE_URL = "http://localhost:5002/api/label" # 暂停定时任务 def pause_service(): response = requests.post(f"{BASE_URL}/background-service/pause") print(response.json()) # 恢复定时任务 def resume_service(): response = requests.post(f"{BASE_URL}/background-service/resume") print(response.json()) # 获取定时任务状态 def get_status(): response = requests.get(f"{BASE_URL}/background-service/status") print(response.json()) ``` ### cURL ```bash # 暂停定时任务 curl -X POST http://localhost:5002/api/label/background-service/pause # 恢复定时任务 curl -X POST http://localhost:5002/api/label/background-service/resume # 获取定时任务状态 curl -X GET http://localhost:5002/api/label/background-service/status ``` --- ## 预期响应 ### 成功暂停 ```json { "status": "success", "message": "后台定时任务已暂停", "data": { "isRunning": false } } ``` ### 成功恢复 ```json { "status": "success", "message": "后台定时任务已恢复", "data": { "isRunning": true } } ``` ### 获取状态 ```json { "status": "success", "message": "获取后台服务状态成功", "data": { "isRunning": true, "service": "LabelPdfCacheBackgroundService", "interval": "5 minutes" } } ``` --- ## 注意事项 1. **数据不会丢失** - 暂停定时任务只是停止周期性执行,已有的待处理任务不会被删除 2. **可以手动处理** - 暂停后可以通过 `/batch-parse` 接口手动触发处理 3. **性能考虑** - 长时间暂停可能导致待处理任务堆积 4. **内存状态** - 如果使用方法3,重启应用后服务会自动恢复运行 5. **监控建议** - 建议定期检查任务状态,确保没有任务堆积 --- ## 常见场景 ### 场景1:维护期间暂停 如需进行数据库维护或其他重要操作,可以暂停定时任务避免并发冲突: ```bash # 暂停 curl -X POST http://localhost:5002/api/label/background-service/pause # 执行维护操作 # ... # 恢复 curl -X POST http://localhost:5002/api/label/background-service/resume ``` ### 场景2:定点手动处理 暂停自动定时任务,改为手动通过API按需处理: ```bash # 暂停自动任务 curl -X POST http://localhost:5002/api/label/background-service/pause # 手动触发解析(当需要时) curl -X POST http://localhost:5002/api/label/batch-parse \ -H "Content-Type: application/json" \ -d '{"mode":"all","limit":500}' ``` ### 场景3:监控异常时暂停 如发现定时任务出现异常,可以快速暂停避免继续出错: ```bash # 检查状态 curl -X GET http://localhost:5002/api/label/background-service/status # 如果出现异常,暂停 curl -X POST http://localhost:5002/api/label/background-service/pause # 调查问题后恢复 curl -X POST http://localhost:5002/api/label/background-service/resume ``` --- ## 故障排查 ### Q: 暂停后定时任务仍在执行 **A:** 可能使用的是方法1或方法2,需要重启应用才能生效 ### Q: 暂停状态在重启后丢失 **A:** 这是正常的。如需持久化暂停状态,可以将状态保存到数据库 ### Q: 恢复后任务堆积 **A:** 这是正常的。系统会逐个处理堆积的任务。可以调整 `ProcessPendingTasksAsync()` 的批处理大小来优化处理速度 --- ## 推荐实施方案 根据部署环境选择: - **开发环境**: 使用方法3(API接口),方便调试 - **生产环境单机**: 使用方法1(配置文件),稳定可靠 - **生产环境Docker**: 使用方法2(环境变量),配置灵活 - **生产环境微服务**: 使用方法3(API接口),需要分布式协调 --- ## 参考信息 - **定时任务执行间隔**: 5分钟(见 `LabelPdfCacheBackgroundService.cs` 第18行) - **处理方法**: `ProcessPendingTasksAsync()` - **处理范围**: 失效缓存、待处理任务、新订单 - **错误处理**: 自动捕获异常,记录日志