线程监测帮助类,可以帮助我们管理task任务
SQL
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for taskinfo -- ---------------------------- DROP TABLE IF EXISTS `taskinfo`; CREATE TABLE `taskinfo` ( `TaskId` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '线程Id', `TaskName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '线程名称', `State` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '线程执行状态', `CurrentTime` datetime NOT NULL COMMENT '运行时间', PRIMARY KEY (`TaskId`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC; SET FOREIGN_KEY_CHECKS = 1;
C# code
/// <summary> /// 线程监测帮助类 /// </summary> public class ThreadTaskMonitor { public static ConcurrentBag<TaskInfo> ThreadTaskList = new ConcurrentBag<TaskInfo>(); private static int IntervalTimeSave = 1000 * 10; //多长时间保存一次数据 private static readonly ILogger<ThreadTaskMonitor> _logger; static ThreadTaskMonitor() { _logger = ServiceLocator.Instance.GetRequiredService<ILogger<ThreadTaskMonitor>>(); Initialize(); SaveData(IntervalTimeSave); } /// <summary> /// 添加任务线程 /// </summary> /// <param name="taskName">线程名称</param> /// <param name="action">线程方法名</param> /// <param name="milliSeconds">毫秒</param> /// <returns>成功返回true,是否返回false</returns> public static bool AddThreadTask(string taskName, Action action, int milliSeconds = 10) { if(ThreadTaskList.Any(t=>t.TaskName == taskName)) { //Trace.Assert(false, $"Task任务中已存在该名称{taskName}的任务!"); Console.WriteLine($"Task任务中已存在该名称{taskName}的任务!"); _logger.LogError($"Task任务中已存在该名称{taskName}的任务!"); return false; } var taskInfo = new TaskInfo(taskName); taskInfo.ActionFunction = action; taskInfo.Task = Task.Run(()=>{ //await Task.Delay(milliSeconds); Thread.Sleep(milliSeconds); action?.Invoke(); }); ThreadTaskList.Add(taskInfo); return true; } /// <summary> /// 添加任务线程 /// </summary> /// <param name="info">任务线程对象</param> /// <returns>成功返回true,是否返回false</returns> public static bool AddThreadTask(TaskInfo info) { if (ThreadTaskList.Any(t => t.TaskName == info.TaskName)) { //Trace.Assert(false, $"Task任务中已存在该名称{info.TaskName}的任务!"); Console.WriteLine($"Task任务中已存在该名称{ info.TaskName}的任务!"); _logger.LogError($"Task任务中已存在该名称{info.TaskName}的任务!"); return false; } ThreadTaskList.Add(info); return true; } /// <summary> /// 初始化开始监测所有线程,如果线程出现异常,则重新启动线程 /// </summary> private static void Initialize() { Task.Run(() => { while (true) { foreach (TaskInfo info in ThreadTaskList) { if (info.Task == null) { info.State = TaskStatus.Faulted.ToString(); info.CurrentTime = DateTime.Now; } else { info.State = info.Task.Status.ToString(); info.CurrentTime = DateTime.Now; if (info.Task.IsFaulted) { try { Thread.Sleep(1000); Debug.WriteLine($"{DateTime.Now}: {info.TaskName} 重新启动!"); _logger.LogInformation($"{DateTime.Now}: {info.TaskName} 重新启动!"); info.Task = Task.Run(() => { info.ActionFunction?.Invoke(); }); info.State = info.Task.Status.ToString(); info.CurrentTime = DateTime.Now; } catch (Exception ex) { _logger.LogError($"任务:{info.TaskName} 启动失败!"); } } } } Thread.Sleep(5000); } }); } /// <summary> /// 将线程状态更新到数据库中 /// </summary> /// <param name="tervalTime"></param> private static void SaveData(int tervalTime) { var _serviceScopeFactory = ServiceLocator.Instance.GetRequiredService<IServiceScopeFactory>(); var _dataContext = _serviceScopeFactory.CreateScope().ServiceProvider.GetRequiredService<DataContext>(); List<TaskInfo> taskInfoAddList = new List<TaskInfo>(); // 新增 List<TaskInfo> taskInfoUpdateList = new List<TaskInfo>(); // 更新 Task.Run(() => { while (true) { try { taskInfoAddList.Clear(); taskInfoUpdateList.Clear(); var taskInfoList = _dataContext.TaskInfos.AsQueryable(); var taskList = ThreadTaskList; TaskInfo taskInfoTemp; foreach (var task in taskList) { taskInfoTemp = taskInfoList.Where(t => t.TaskName == task.TaskName).FirstOrDefault(); if (task.State == null) task.State = "Created"; if (taskInfoTemp == null) { taskInfoAddList.Add(task); } else { taskInfoTemp.State = task.State; taskInfoTemp.CurrentTime = task.CurrentTime; taskInfoUpdateList.Add(taskInfoTemp); } //Console.WriteLine(task.ToString()); } if (taskInfoAddList.Count > 0) _dataContext.TaskInfos.AddRange(taskInfoAddList); if (taskInfoUpdateList.Count > 0) _dataContext.TaskInfos.UpdateRange(taskInfoUpdateList); if (!taskList.IsEmpty) _dataContext.SaveChanges(); } catch (Exception ex) { //Console.WriteLine(ex.Message); _logger.LogError("线程监控更新数据异常:" + ex.Message); } Thread.Sleep(tervalTime); } }); } }
优化和重构代码是提升代码质量、可维护性和性能的重要步骤。以下是对您提供的 ThreadTaskMonitor
类的详细优化和重构建议,包括代码示例和专业分析。
主要优化和重构点
-
使用
CancellationToken
管理任务的生命周期:- 通过引入
CancellationToken
,可以优雅地停止任务监控和数据保存,避免使用无限循环和Thread.Sleep
。
- 通过引入
-
减少代码重复:
AddThreadTask
方法有两个重载,逻辑重复。可以通过合并这两个方法来减少代码重复。
-
使用
async/await
代替Thread.Sleep
:- 使用
Task.Delay
替代Thread.Sleep
,使代码更具可读性和异步友好。
- 使用
-
引入
ConcurrentDictionary
替代ConcurrentBag
:- 使用
ConcurrentDictionary
可以更高效地查找和管理任务,避免在添加任务时进行遍历。
- 使用
-
改进异常处理:
- 细化异常处理,确保异常信息能够提供更多上下文,并在必要时进行重试。
-
使用
IHostedService
或BackgroundService
:- 如果这是一个 ASP.NET Core 应用,可以将监控逻辑封装到一个后台服务中,利用 DI 和生命周期管理。
重构后的代码示例
using System; using System.Collections.Concurrent; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; public class ThreadTaskMonitor : IHostedService { private readonly ConcurrentDictionary<string, TaskInfo> _threadTaskList = new ConcurrentDictionary<string, TaskInfo>(); private readonly ILogger<ThreadTaskMonitor> _logger; private CancellationTokenSource _cancellationTokenSource; public ThreadTaskMonitor(ILogger<ThreadTaskMonitor> logger) { _logger = logger; } public Task StartAsync(CancellationToken cancellationToken) { _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); InitializeMonitoring(_cancellationTokenSource.Token); SaveDataAsync(_cancellationTokenSource.Token); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { _cancellationTokenSource.Cancel(); return Task.CompletedTask; } public bool AddThreadTask(string taskName, Action action, int milliSeconds = 10) { if (_threadTaskList.ContainsKey(taskName)) { _logger.LogError($"Task with name {taskName} already exists!"); return false; } var taskInfo = new TaskInfo(taskName) { ActionFunction = action, Task = Task.Run(async () => { await Task.Delay(milliSeconds); action?.Invoke(); }) }; _threadTaskList[taskName] = taskInfo; return true; } private void InitializeMonitoring(CancellationToken cancellationToken) { Task.Run(async () => { while (!cancellationToken.IsCancellationRequested) { foreach (var info in _threadTaskList.Values) { if (info.Task.IsFaulted || info.Task.IsCanceled) { _logger.LogInformation($"{info.TaskName} is restarting!"); info.Task = Task.Run(info.ActionFunction); } info.State = info.Task.Status.ToString(); info.CurrentTime = DateTime.Now; } await Task.Delay(5000, cancellationToken); } }, cancellationToken); } private async Task SaveDataAsync(CancellationToken cancellationToken) { var _serviceScopeFactory = ServiceLocator.Instance.GetRequiredService<IServiceScopeFactory>(); var _dataContext = _serviceScopeFactory.CreateScope().ServiceProvider.GetRequiredService<DataContext>(); while (!cancellationToken.IsCancellationRequested) { try { var taskInfoAddList = _threadTaskList.Values.Where(t => t.State == null).ToList(); var taskInfoUpdateList = _threadTaskList.Values .Where(t => t.State != null && _dataContext.TaskInfos.Any(dbTask => dbTask.TaskName == t.TaskName)) .ToList(); if (taskInfoAddList.Any()) await _dataContext.TaskInfos.AddRangeAsync(taskInfoAddList, cancellationToken); if (taskInfoUpdateList.Any()) { foreach (var task in taskInfoUpdateList) { var dbTask = await _dataContext.TaskInfos.FirstOrDefaultAsync(t => t.TaskName == task.TaskName, cancellationToken); if (dbTask != null) { dbTask.State = task.State; dbTask.CurrentTime = task.CurrentTime; } } } await _dataContext.SaveChangesAsync(cancellationToken); } catch (Exception ex) { _logger.LogError($"Error updating task data: {ex.Message}"); } await Task.Delay(10000, cancellationToken); // Save data every 10 seconds } } }
详细分析
-
使用
IHostedService
:- 通过实现
IHostedService
,类可以在 ASP.NET Core 中作为后台服务运行,确保在应用程序的生命周期内管理任务。
- 通过实现
-
引入
CancellationTokenSource
:- 允许优雅地停止监控和保存任务,避免无休止的循环。
-
使用
ConcurrentDictionary
:- 提高性能并简化任务的查找和管理。
-
合并任务添加逻辑:
- 通过检查
ContainsKey
来避免重复代码,简化AddThreadTask
方法。
- 通过检查
-
异步编程:
- 使用
async/await
使得代码在等待时不会阻塞线程,提高响应能力。
- 使用
-
异常处理的改进:
- 提供更多上下文信息,并在数据保存时使用
async
方法,确保数据库操作的非阻塞性。
- 提供更多上下文信息,并在数据保存时使用
通过这些优化和重构,代码的可读性、可维护性和性能都有显著提升。
public interface IService { Task ExecServiceAsync(); } public class WorkerHelper : BackgroundService { private readonly ILogger<Worker> _logger; private readonly IServiceScopeFactory _serviceScopeFactory; public Worker(ILogger<Worker> logger, IServiceScopeFactory serviceScopeFactory) { _logger = logger; _serviceScopeFactory = serviceScopeFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var services = _serviceScopeFactory.CreateScope().ServiceProvider.GetServices<IService>(); foreach (var service in services) { (service as IService)?.ExecServiceAsync(); } } } services.AddHostedService<WorkerHelper>();
以下是对重构后的 ThreadTaskMonitor
类的使用示例,包括详细的代码和解释。这个使用示例将展示如何在一个 ASP.NET Core 应用中集成和使用该类。
使用示例
1. 在 ASP.NET Core 项目中配置服务
首先,我们需要在 ASP.NET Core Web 应用中配置服务,以便能够使用 ThreadTaskMonitor
。可以通过修改 Startup.cs
文件来实现。
Startup.cs
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddLogging(); // 增加Logging服务 services.AddDbContext<DataContext>(options => options.UseSqlServer("YourConnectionString")); // 配置Entity Framework数据库上下文 // 注册ThreadTaskMonitor作为后台服务 services.AddSingleton<ThreadTaskMonitor>(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // Your existing middleware setup // Start monitoring when the application starts var monitor = app.ApplicationServices.GetRequiredService<ThreadTaskMonitor>(); monitor.StartAsync(CancellationToken.None).GetAwaiter().GetResult(); } }
在上面的代码中,我们将 ThreadTaskMonitor
注册为单例服务。应用程序启动时,将会自动启动任务监控。
2. 添加一个控制器
接下来,我们可以创建一个控制器来演示如何添加和管理线程任务。
TaskController.cs
using Microsoft.AspNetCore.Mvc; [Route("api/[controller]")] [ApiController] public class TaskController : ControllerBase { private readonly ThreadTaskMonitor _taskMonitor; public TaskController(ThreadTaskMonitor taskMonitor) { _taskMonitor = taskMonitor; } /// <summary> /// 添加一个新的任务 /// </summary> [HttpPost("add")] public IActionResult AddTask(string taskName, int delaySeconds) { bool isSuccess = _taskMonitor.AddThreadTask(taskName, () => { Console.WriteLine($"{taskName}执行中..."); // 模拟任务执行,例如处理一些逻辑 }, delaySeconds * 1000); // 转换为毫秒 if (isSuccess) return Ok($"Task '{taskName}' added successfully."); else return BadRequest($"Task '{taskName}' already exists."); } /// <summary> /// 显示当前所有任务状态 /// </summary> [HttpGet("status")] public IActionResult GetTasksStatus() { // 这里可以返回任务状态,或者可以返回一个简化的数据模型 return Ok(_taskMonitor.GetCurrentTasks()); } }
在这个控制器中,我们定义了两个端点:
AddTask
:可以添加新的任务,并指定延迟时间。GetTasksStatus
:获取当前所有任务的状态。
3. 任务信息类 TaskInfo
确保定义 TaskInfo
类,它应该包含任务的状态和其他需要的信息。下面是一个基本的 TaskInfo
类的示例:
TaskInfo.cs
using System; public class TaskInfo { public string TaskName { get; set; } public Action ActionFunction { get; set; } public Task Task { get; set; } public string State { get; set; } public DateTime CurrentTime { get; set; } public TaskInfo(string taskName) { TaskName = taskName; State = "Created"; CurrentTime = DateTime.Now; } } /// <summary> /// 表示线程任务信息类 /// </summary> [Serializable] [Table("taskinfo")] public class TaskInfo: TopBasePoco { public TaskInfo() { } public TaskInfo(string taskName) { ID = Guid.NewGuid().ToString("N"); TaskName = taskName; CurrentTime = DateTime.Now; } /// <summary> /// 表示执行的线程 /// </summary> [NotMapped] public Task Task { get; set; } /// <summary> /// 线程Id /// </summary> [Display(Name = "线程Id")] [Key] [Column("TaskId")] public new string ID { get; set; } /// <summary> /// 线程名称 /// </summary> [Display(Name = "线程名称")] [StringLength(255, ErrorMessage = "{0}最多输入{1}个字符")] public string TaskName { get; set; } /// <summary> /// 线程执行状态 /// </summary> [StringLength(30,ErrorMessage="{0}最多输入{1}个字符")] [Display(Name = "执行状态")] public string State { get; set; } /// <summary> /// 当前时间 /// </summary> [Display(Name = "当前时间")] public DateTime CurrentTime { get; set; } [NotMapped] public Action ActionFunction { get; set; } }
4. 运行和测试应用程序
- 启动您的 ASP.NET Core 应用程序。
- 通过 Postman、cURL 或浏览器访问以下 URL 添加任务:
-
添加任务:
POST http://localhost:5000/api/task/add?taskName=MyTask1&delaySeconds=5
- 获取任务状态:
-
GET http://localhost:5000/api/task/status
将会看到响应中返回已添加的任务以及它们的状态,例如“执行中”“已完成”等。
详细描述过程
-
Service Configuration:
通过将ThreadTaskMonitor
注册为 Singleton 服务,我们确保在整个应用生命周期内仅有一个实例。这使得后台任务监控在应用的不同组件中都是共享和可访问的。 -
Controller for Task Management:
TaskController
能够响应 HTTP 请求,并将任务调度逻辑暴露给外部。通过 POST 请求,可以动态添加任务,同时通过 GET 请求查看当前任务的状态。 -
Task Execution:
每当添加任务时,ThreadTaskMonitor
会立即调度相应的动作,并在指定的延迟之后执行。任务的执行状态会被自动监控,并可以在状态列表中获得反馈。
总结
这种使用示例展示了 ThreadTaskMonitor
的集成和利用,能够动态添加任务并监控其状态。我们强调了异步编程的使用、服务注册、以及 API 设计,使这个系统不仅高效而且易维护。这种方式相对灵活,适合在多种业务场景中使用。
当然,可以将 IHostedService
改成 BackgroundService
。使用 BackgroundService
是更现代的做法,因为它提供了一种更简洁的方式来处理后台任务,特别是在 ASP.NET Core 应用中。
修改后的 ThreadTaskMonitor
类
我们将 ThreadTaskMonitor
类继承自 BackgroundService
,并重构相应的方法。以下是更新后的代码:
using System; using System.Collections.Concurrent; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; public class ThreadTaskMonitor : BackgroundService { private readonly ConcurrentDictionary<string, TaskInfo> _threadTaskList = new ConcurrentDictionary<string, TaskInfo>(); private readonly IServiceScopeFactory _serviceScopeFactory; private readonly ILogger<ThreadTaskMonitor> _logger; public ThreadTaskMonitor(IServiceScopeFactory serviceScopeFactory, ILogger<ThreadTaskMonitor> logger) { _serviceScopeFactory = serviceScopeFactory; _logger = logger; } public bool AddThreadTask(string taskName, Action action, int milliSeconds = 10) { if (_threadTaskList.ContainsKey(taskName)) { _logger.LogError($"Task with name {taskName} already exists!"); return false; } var taskInfo = new TaskInfo(taskName) { ActionFunction = action, Task = Task.Run(async () => { await Task.Delay(milliSeconds); action?.Invoke(); }) }; _threadTaskList[taskName] = taskInfo; return true; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var monitoringTask = MonitorTaskStatus(stoppingToken); var saveDataTask = SaveDataAsync(stoppingToken); await Task.WhenAll(monitoringTask, saveDataTask); } private async Task MonitorTaskStatus(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { foreach (var info in _threadTaskList.Values) { if (info.Task.IsFaulted || info.Task.IsCanceled) { _logger.LogInformation($"{info.TaskName} is restarting!"); info.Task = Task.Run(info.ActionFunction); } info.State = info.Task.Status.ToString(); info.CurrentTime = DateTime.Now; } await Task.Delay(5000, stoppingToken); } } private async Task SaveDataAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using (var scope = _serviceScopeFactory.CreateScope()) { var _dataContext = scope.ServiceProvider.GetRequiredService<DataContext>(); try { var taskInfoAddList = _threadTaskList.Values .Where(t => t.State == null).ToList(); var taskInfoUpdateList = _threadTaskList.Values .Where(t => t.State != null && _dataContext.TaskInfos.Any(dbTask => dbTask.TaskName == t.TaskName)) .ToList(); if (taskInfoAddList.Any()) await _dataContext.TaskInfos.AddRangeAsync(taskInfoAddList, stoppingToken); if (taskInfoUpdateList.Any()) { foreach (var task in taskInfoUpdateList) { var dbTask = await _dataContext.TaskInfos.FirstOrDefaultAsync(t => t.TaskName == task.TaskName, stoppingToken); if (dbTask != null) { dbTask.State = task.State; dbTask.CurrentTime = task.CurrentTime; } } } await _dataContext.SaveChangesAsync(stoppingToken); } catch (Exception ex) { _logger.LogError($"Error updating task data: {ex.Message}"); } } await Task.Delay(10000, stoppingToken); // 每10秒保存一次数据 } } }
详细修改分析
1. 继承 BackgroundService
public class ThreadTaskMonitor : BackgroundService
通过继承 BackgroundService
,我们利用它提供的便利结构来简化后台任务的执行逻辑和管理细节。
2. 重写 ExecuteAsync
方法
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var monitoringTask = MonitorTaskStatus(stoppingToken);
var saveDataTask = SaveDataAsync(stoppingToken);
await Task.WhenAll(monitoringTask, saveDataTask);
}
ExecuteAsync
方法是 BackgroundService
必须实现的一个方法,它在服务启动时自动调用。我们在其中启动监控和保存数据的任务,并等待两者完成。
3. 任务监控方法
private async Task MonitorTaskStatus(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { // ... } }
这个方法循环检查所有任务的状态。如果任务被标记为 Faulted
或 Canceled
,则重新启动该任务。整个过程使用 await Task.Delay()
来避免阻塞。
4. 数据保存方法
private async Task SaveDataAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
// ...
}
await Task.Delay(10000, stoppingToken); // 每10秒保存一次数据
}
}
同样,这个方法在后台持续运行,并定期通过 Entity Framework 将线程状态更新到数据库中。使用 CancellationToken
确保可以干净地停止数据保存操作。
更新使用示例
1. 服务注册
在 Startup.cs
中,改为注册 ThreadTaskMonitor
类:
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(); // 增加Logging服务
services.AddDbContext<DataContext>(options =>
options.UseSqlServer("YourConnectionString")); // 配置Entity Framework数据库上下文
// 注册ThreadTaskMonitor作为后台服务
services.AddHostedService<ThreadTaskMonitor>();
}
2. 控制器和任务的管理
控制器部分无须任何修改,可以保留之前的代码。
总结
通过将 ThreadTaskMonitor
类改为继承自 BackgroundService
,我们使代码更加简洁,并利用了 BackgroundService
为后台服务提供的基础结构。这种方式不仅提高了可读性,还确保了任务的可靠性和可维护性,可以适应更加复杂的应用场景。整体服务设置更加符合现代 ASP.NET Core 应用的最佳实践。