429 lines
19 KiB
C#
429 lines
19 KiB
C#
using CommunityToolkit.Mvvm.Messaging;
|
||
using Cowain.Base.Helpers;
|
||
using Cowain.Base.ViewModels;
|
||
using Microsoft.Extensions.Logging;
|
||
using Plugin.Cowain.Base.Models;
|
||
using Plugin.Cowain.Driver.Abstractions;
|
||
using Plugin.Cowain.Driver.IServices;
|
||
using Plugin.Cowain.Driver.ViewModels;
|
||
using System.Text.Json;
|
||
using System.Threading.Tasks.Dataflow;
|
||
|
||
namespace Plugin.Cowain.Driver;
|
||
|
||
public class AlarmHostedService : BackgroundHostedService
|
||
{
|
||
private readonly IMessenger _messenger; // MVVM工具包的消息器
|
||
private readonly IAlarmService _alarmService;
|
||
private readonly ILogger<AlarmHostedService> _logger;
|
||
private IDeviceMonitor _deviceMonitor;
|
||
private readonly IAlarmGroupService _alarmGroupService;
|
||
private readonly IAlarmLevelService _alarmLevelService;
|
||
// 缓存系统报警组ID和错误报警级别ID(避免重复查询数据库)
|
||
private int _systemAlarmGroupId = 1;
|
||
private int _errorAlarmLevelId = 1;
|
||
|
||
private List<AlarmGroupViewModel>? alarmGroups;
|
||
private List<AlarmLevelViewModel>? alarmLevels;
|
||
|
||
private readonly SemaphoreSlim _alarmsSemaphore = new(1, 1);
|
||
private readonly SemaphoreSlim _cacheSemaphore = new(1, 1); // 保护缓存初始化的信号量
|
||
|
||
public AlarmHostedService(IDeviceMonitor deviceMonitor, IAlarmService alarmService, IAlarmGroupService alarmGroupService, IMessenger messenger,
|
||
IAlarmLevelService alarmLevelService, ILogger<AlarmHostedService> logger)
|
||
{
|
||
_deviceMonitor = deviceMonitor;
|
||
_alarmService = alarmService;
|
||
_alarmGroupService = alarmGroupService;
|
||
_alarmLevelService = alarmLevelService;
|
||
_messenger = messenger;
|
||
_logger = logger;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化报警配置缓存(查询"系统"组ID和"错误"级别ID)
|
||
/// </summary>
|
||
private async Task InitAlarmConfigCacheAsync(CancellationToken cancellationToken)
|
||
{
|
||
// 新增:处理初始化时的取消异常
|
||
if (cancellationToken.IsCancellationRequested) return;
|
||
|
||
await _cacheSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||
try
|
||
{
|
||
// 查询名称为"系统"的报警组ID
|
||
alarmGroups = await _alarmGroupService.GetAllAsync().ConfigureAwait(false);
|
||
var systemGroup = alarmGroups.FirstOrDefault(g => string.Equals(g.Name, "系统", StringComparison.Ordinal));
|
||
if (systemGroup != null)
|
||
{
|
||
_systemAlarmGroupId = systemGroup.Id;
|
||
_logger.LogInformation($"成功获取「系统」报警组ID:{_systemAlarmGroupId}");
|
||
}
|
||
else
|
||
{
|
||
_logger.LogWarning("未查询到名称为「系统」的报警组,将使用默认ID:1");
|
||
}
|
||
|
||
// 查询名称为"错误"的报警级别ID
|
||
alarmLevels = await _alarmLevelService.GetAllAsync().ConfigureAwait(false);
|
||
var errorLevel = alarmLevels.FirstOrDefault(l => string.Equals(l.Name, "错误", StringComparison.Ordinal));
|
||
if (errorLevel != null)
|
||
{
|
||
_errorAlarmLevelId = errorLevel.Id;
|
||
_logger.LogInformation($"成功获取「错误」报警级别ID:{_errorAlarmLevelId}");
|
||
}
|
||
else
|
||
{
|
||
_logger.LogWarning("未查询到名称为「错误」的报警级别,将使用默认ID:1");
|
||
}
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
// 正常取消,仅日志记录,不抛异常
|
||
_logger.LogInformation("报警配置缓存初始化被取消");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "初始化报警配置缓存失败,将使用默认ID:1");
|
||
}
|
||
finally
|
||
{
|
||
// 新增:确保信号量释放(即使取消也释放)
|
||
if (_cacheSemaphore.CurrentCount == 0)
|
||
{
|
||
_cacheSemaphore.Release();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
[LogAndSwallow]
|
||
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||
{
|
||
var t = Task.Factory.StartNew(async () =>
|
||
{
|
||
try
|
||
{
|
||
// 初始化报警配置缓存(仅执行一次)
|
||
await InitAlarmConfigCacheAsync(stoppingToken).ConfigureAwait(false);
|
||
// 延时5秒,等待PLC连接完成
|
||
await Task.Delay(5000, stoppingToken).ConfigureAwait(false);
|
||
while (!stoppingToken.IsCancellationRequested)
|
||
{
|
||
try //内层try捕获单次循环的取消异常
|
||
{
|
||
// 并行遍历设备集合
|
||
// 修改1:内层用ct而非stoppingToken,避免令牌混用
|
||
await Parallel.ForEachAsync(_deviceMonitor.Devices, stoppingToken, async (dev, ct) =>
|
||
{
|
||
// 检查内层令牌是否取消
|
||
if (ct.IsCancellationRequested) return;
|
||
|
||
if (!dev.IsConnected)
|
||
{
|
||
// 通信故障发生 - 使用查询到的Group和LevelID(替代硬编码的1)
|
||
var newAlarm = new AlarmViewModel
|
||
{
|
||
TagId = 100000 + dev.Id,
|
||
StartTime = DateTime.Now,
|
||
Status = true,
|
||
Group = _systemAlarmGroupId, // 从缓存获取"系统"组ID
|
||
Level = _errorAlarmLevelId, // 从缓存获取"错误"级别ID
|
||
GroupName = alarmGroups?.FirstOrDefault(g => g.Id == _systemAlarmGroupId)?.Name ?? "系统",
|
||
LevelName = alarmLevels?.FirstOrDefault(l => l.Id == _errorAlarmLevelId)?.Name ?? "错误",
|
||
Color = alarmLevels?.FirstOrDefault(l => l.Id == _errorAlarmLevelId)?.Color ?? "Red",
|
||
Desc = $"{dev.DeviceName}:设备通信故障",
|
||
};
|
||
// 修改2:传递内层ct而非stoppingToken
|
||
await AddAlarmAsync(newAlarm, ct).ConfigureAwait(false);
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
// 通信恢复,清除通信报警
|
||
var clearAlarm = new AlarmViewModel
|
||
{
|
||
TagId = 100000 + dev.Id,
|
||
};
|
||
// 修改2:传递内层ct而非stoppingToken
|
||
await RemoveAlarmAsync(clearAlarm, ct).ConfigureAwait(false);
|
||
}
|
||
|
||
// 快照变量列表并筛选启用报警的变量,避免其他线程修改集合
|
||
var alarmVariables = (dev.Variables ?? Enumerable.Empty<VariableViewModel>()).Where(v => v.AlarmEnable).ToList();
|
||
|
||
// 并行处理每个变量的报警逻辑
|
||
// 修改3:内层Parallel用ctInner,传递ctInner到异步方法
|
||
await Parallel.ForEachAsync(alarmVariables, ct, async (item, ctInner) =>
|
||
{
|
||
if (ctInner.IsCancellationRequested) return;
|
||
|
||
if (!item.IsSuccess || string.IsNullOrEmpty(item.Value))
|
||
{
|
||
var clearAlarm = new AlarmViewModel
|
||
{
|
||
TagId = item.Id,
|
||
};
|
||
await RemoveAlarmAsync(clearAlarm, ctInner).ConfigureAwait(false);
|
||
}
|
||
else
|
||
{
|
||
if (item.Value == item.AlarmValue)
|
||
{
|
||
var newAlarm = new AlarmViewModel
|
||
{
|
||
TagId = item.Id,
|
||
StartTime = DateTime.Now,
|
||
Status = true,
|
||
Group = item.AlarmGroup,
|
||
Level = item.AlarmLevel,
|
||
GroupName = alarmGroups?.FirstOrDefault(g => g.Id == item.AlarmGroup)?.Name ?? string.Empty,
|
||
LevelName = alarmLevels?.FirstOrDefault(l => l.Id == item.AlarmLevel)?.Name ?? string.Empty,
|
||
Color = alarmLevels?.FirstOrDefault(l => l.Id == item.AlarmLevel)?.Color ?? "Red",
|
||
Desc = $"{item.Address}-{item.AlarmMsg}",
|
||
};
|
||
await AddAlarmAsync(newAlarm, ctInner).ConfigureAwait(false);
|
||
}
|
||
else
|
||
{
|
||
var clearAlarm = new AlarmViewModel
|
||
{
|
||
TagId = item.Id,
|
||
};
|
||
await RemoveAlarmAsync(clearAlarm, ctInner).ConfigureAwait(false);
|
||
}
|
||
}
|
||
}).ConfigureAwait(false);
|
||
}).ConfigureAwait(false);
|
||
// 检查取消状态,避免无效操作
|
||
if (stoppingToken.IsCancellationRequested) break;
|
||
var alarms = await GetAlarmsAsync(stoppingToken).ConfigureAwait(false);
|
||
_messenger.Send(new AlarmChangedMessage(alarms));
|
||
|
||
// 修改4:Task.Delay添加try-catch,处理取消异常
|
||
try
|
||
{
|
||
await Task.Delay(500, stoppingToken).ConfigureAwait(false);
|
||
}
|
||
catch (TaskCanceledException)
|
||
{
|
||
// Delay被取消,跳出循环
|
||
break;
|
||
}
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
// 单次循环被取消,跳出外层循环
|
||
_logger.LogInformation("报警服务循环执行被取消");
|
||
break;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 非取消异常,记录日志并继续循环
|
||
_logger.LogError(ex, "报警服务单次循环执行异常,将继续下一次循环");
|
||
await Task.Delay(500, stoppingToken).ConfigureAwait(false);
|
||
}
|
||
}
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
// 正常关闭时的取消异常,仅记录日志,不抛异常
|
||
_logger.LogInformation("AlarmHostedService 执行线程已正常取消");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "AlarmHostedService 执行线程发生未预期异常");
|
||
}
|
||
|
||
}, stoppingToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
|
||
return t;
|
||
}
|
||
|
||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||
{
|
||
_logger.LogInformation("AlarmHostedService StopAsync 开始执行");
|
||
|
||
try
|
||
{
|
||
if (ExecuteTask != null && !ExecuteTask.IsCompleted)
|
||
{
|
||
// 修改5:优化取消令牌逻辑,避免重复取消
|
||
using var stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||
try
|
||
{
|
||
// 正确使用异步操作并传递取消令牌
|
||
var stopTasks = _deviceMonitor.DeviceThreads
|
||
.Select(hostTask => hostTask.StopReadAsync(stoppingCts.Token))
|
||
.ToList();
|
||
|
||
// 新增:超时保护,避免等待过久
|
||
await Task.WhenAll(stopTasks).WaitAsync(TimeSpan.FromSeconds(5), stoppingCts.Token).ConfigureAwait(false);
|
||
_logger.LogInformation("AlarmHostedService 所有设备线程已停止");
|
||
}
|
||
catch (TaskCanceledException)
|
||
{
|
||
_logger.LogInformation("设备线程停止操作被取消");
|
||
}
|
||
catch (TimeoutException)
|
||
{
|
||
_logger.LogWarning("设备线程停止操作超时");
|
||
}
|
||
|
||
try
|
||
{
|
||
// 修改6:优雅等待执行任务结束,SuppressThrowing避免抛出取消异常
|
||
await ExecuteTask.WaitAsync(stoppingCts.Token).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
|
||
}
|
||
catch
|
||
{
|
||
// 忽略等待时的所有异常
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "AlarmHostedService StopAsync 执行异常");
|
||
}
|
||
finally
|
||
{
|
||
_logger.LogInformation("AlarmHostedService StopAsync 执行完成");
|
||
// 确保调用基类StopAsync,释放HostedService资源
|
||
await base.StopAsync(cancellationToken).ConfigureAwait(false);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加故障
|
||
/// </summary>
|
||
public async Task AddAlarmAsync(AlarmViewModel alarm, CancellationToken cancellationToken)
|
||
{
|
||
// 新增:检查取消状态,避免无效操作
|
||
if (cancellationToken.IsCancellationRequested) return;
|
||
|
||
await _alarmsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||
try
|
||
{
|
||
var exist = _deviceMonitor.Alarms.FirstOrDefault(a => a.TagId == alarm.TagId);
|
||
if (exist == null)
|
||
{
|
||
_deviceMonitor.Alarms.Add(alarm);
|
||
if (_alarmService != null)
|
||
{
|
||
var inAlarm = await _alarmService.GetInAlarmAsync(alarm.TagId).ConfigureAwait(false);
|
||
if (inAlarm == null)
|
||
{
|
||
var addResult = await _alarmService.AddAsync(alarm).ConfigureAwait(false);
|
||
if (!addResult.IsSuccess)
|
||
{
|
||
_logger.LogError($"添加历史报警异常:{JsonSerializer.Serialize(alarm)}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
_logger.LogInformation("添加报警操作被取消");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, $"添加报警异常:{alarm.TagId}");
|
||
}
|
||
finally
|
||
{
|
||
// 新增:确保信号量释放(即使取消/异常)
|
||
if (_alarmsSemaphore.CurrentCount == 0)
|
||
{
|
||
_alarmsSemaphore.Release();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除故障
|
||
/// </summary>
|
||
public async Task RemoveAlarmAsync(AlarmViewModel alarm, CancellationToken cancellationToken)
|
||
{
|
||
// 新增:检查取消状态,避免无效操作
|
||
if (cancellationToken.IsCancellationRequested) return;
|
||
|
||
await _alarmsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||
try
|
||
{
|
||
var exist = _deviceMonitor.Alarms.FirstOrDefault(a => a.TagId == alarm.TagId);
|
||
if (exist != null)
|
||
{
|
||
//实时报警列表中有此报警
|
||
_deviceMonitor.Alarms.Remove(exist);
|
||
if (_alarmService != null)
|
||
{
|
||
var inAlarm = await _alarmService.GetInAlarmAsync(alarm.TagId).ConfigureAwait(false);
|
||
if (inAlarm != null)
|
||
{
|
||
//数据中有此报警,添加解除记录
|
||
var removeResult = await _alarmService.CancelAsync(inAlarm).ConfigureAwait(false);
|
||
if (!removeResult.IsSuccess)
|
||
{
|
||
_logger.LogError($"取消历史报警异常:{JsonSerializer.Serialize(alarm)}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//实时报警列表中无此报警,还需要查询历史报警记录
|
||
var inAlarm = await _alarmService.GetInAlarmAsync(alarm.TagId).ConfigureAwait(false);
|
||
if (inAlarm != null)
|
||
{
|
||
//数据中有此报警,添加解除记录
|
||
var removeResult = await _alarmService.CancelAsync(alarm).ConfigureAwait(false);
|
||
if (!removeResult.IsSuccess)
|
||
{
|
||
_logger.LogError($"取消历史报警异常:{JsonSerializer.Serialize(alarm)}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
_logger.LogInformation("移除报警操作被取消");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, $"移除报警异常:{alarm.TagId}");
|
||
}
|
||
finally
|
||
{
|
||
// 新增:确保信号量释放(即使取消/异常)
|
||
if (_alarmsSemaphore.CurrentCount == 0)
|
||
{
|
||
_alarmsSemaphore.Release();
|
||
}
|
||
}
|
||
}
|
||
|
||
public async Task<List<AlarmViewModel>> GetAlarmsAsync(CancellationToken cancellationToken)
|
||
{
|
||
// 新增:检查取消状态,避免无效操作
|
||
if (cancellationToken.IsCancellationRequested) return new List<AlarmViewModel>();
|
||
|
||
await _alarmsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||
try
|
||
{
|
||
return [.. _deviceMonitor.Alarms];
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
_logger.LogInformation("获取报警列表操作被取消");
|
||
return new List<AlarmViewModel>();
|
||
}
|
||
finally
|
||
{
|
||
// 新增:确保信号量释放(即使取消/异常)
|
||
if (_alarmsSemaphore.CurrentCount == 0)
|
||
{
|
||
_alarmsSemaphore.Release();
|
||
}
|
||
}
|
||
}
|
||
} |