线程监测帮助类,可以帮助我们管理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 应用的最佳实践。
浙公网安备 33010602011771号