Protocol376Simulator/Services/HeartbeatService.cs
2025-05-08 17:26:10 +08:00

216 lines
7.1 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
namespace Protocol376Simulator.Services
{
/// <summary>
/// 心跳服务类,负责处理定时发送心跳
/// </summary>
public class HeartbeatService
{
private readonly string _deviceIdentifier;
private readonly Func<Task> _heartbeatAction;
private CancellationTokenSource _heartbeatCancellationTokenSource;
private Task _heartbeatTask;
private readonly object _heartbeatLock = new object();
private bool _isRunning = false;
private TimeSpan _heartbeatInterval = TimeSpan.FromMinutes(4); // 默认心跳间隔4分钟
private int _successfulHeartbeats = 0;
private DateTime _lastHeartbeatTime = DateTime.MinValue;
/// <summary>
/// 当心跳发送成功时触发
/// </summary>
public event EventHandler<DateTime> HeartbeatSent;
/// <summary>
/// 成功的心跳次数
/// </summary>
public int SuccessfulHeartbeats => _successfulHeartbeats;
/// <summary>
/// 最后一次心跳时间
/// </summary>
public DateTime LastHeartbeatTime => _lastHeartbeatTime;
/// <summary>
/// 心跳间隔
/// </summary>
public TimeSpan HeartbeatInterval
{
get => _heartbeatInterval;
set => _heartbeatInterval = value;
}
/// <summary>
/// 心跳是否正在运行
/// </summary>
public bool IsRunning => _isRunning;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="deviceIdentifier">设备标识(用于日志)</param>
/// <param name="heartbeatAction">执行心跳的委托函数</param>
public HeartbeatService(string deviceIdentifier, Func<Task> heartbeatAction)
{
_deviceIdentifier = deviceIdentifier;
_heartbeatAction = heartbeatAction ?? throw new ArgumentNullException(nameof(heartbeatAction));
}
/// <summary>
/// 启动心跳服务
/// </summary>
public void Start()
{
lock (_heartbeatLock)
{
if (_isRunning)
{
return;
}
_heartbeatCancellationTokenSource?.Cancel();
_heartbeatCancellationTokenSource = new CancellationTokenSource();
_heartbeatTask = RunHeartbeatAsync(_heartbeatCancellationTokenSource.Token);
_isRunning = true;
Log.Information("{DeviceId} 已启动心跳服务,间隔: {Interval}分钟",
_deviceIdentifier, _heartbeatInterval.TotalMinutes);
}
}
/// <summary>
/// 停止心跳服务
/// </summary>
public void Stop()
{
lock (_heartbeatLock)
{
if (!_isRunning)
{
return;
}
_heartbeatCancellationTokenSource?.Cancel();
_isRunning = false;
Log.Information("{DeviceId} 已停止心跳服务", _deviceIdentifier);
}
}
/// <summary>
/// 重置心跳计数
/// </summary>
public void ResetHeartbeatCount()
{
_successfulHeartbeats = 0;
}
/// <summary>
/// 记录心跳成功
/// </summary>
public void RecordHeartbeatSuccess()
{
_successfulHeartbeats++;
_lastHeartbeatTime = DateTime.Now;
HeartbeatSent?.Invoke(this, _lastHeartbeatTime);
}
/// <summary>
/// 设置心跳间隔
/// </summary>
/// <param name="minutes">间隔分钟数</param>
public void SetHeartbeatInterval(double minutes)
{
if (minutes <= 0)
{
throw new ArgumentException("心跳间隔必须大于0分钟", nameof(minutes));
}
_heartbeatInterval = TimeSpan.FromMinutes(minutes);
Log.Information("{DeviceId} 心跳间隔已设置为 {Interval}分钟", _deviceIdentifier, minutes);
// 如果心跳正在运行,重启心跳任务以应用新的间隔
if (_isRunning)
{
Stop();
Start();
}
}
/// <summary>
/// 运行心跳任务
/// </summary>
private async Task RunHeartbeatAsync(CancellationToken cancellationToken)
{
try
{
// 初始等待一段随机时间,避免多个设备同时发送心跳
int initialDelayMs = new Random().Next(1000, 5000);
await Task.Delay(initialDelayMs, cancellationToken);
while (!cancellationToken.IsCancellationRequested)
{
try
{
// 执行心跳操作
await _heartbeatAction();
// 记录心跳成功
RecordHeartbeatSuccess();
Log.Debug("{DeviceId} 心跳已发送,下一次将在 {NextTime} 发送",
_deviceIdentifier, DateTime.Now.Add(_heartbeatInterval).ToString("HH:mm:ss"));
}
catch (Exception ex)
{
Log.Error(ex, "{DeviceId} 发送心跳时发生错误: {ErrorMessage}",
_deviceIdentifier, ex.Message);
}
// 等待下一次心跳
await Task.Delay(_heartbeatInterval, cancellationToken);
}
}
catch (OperationCanceledException)
{
// 任务被取消,正常退出
Log.Debug("{DeviceId} 心跳任务已取消", _deviceIdentifier);
}
catch (Exception ex)
{
Log.Error(ex, "{DeviceId} 心跳任务发生异常: {ErrorMessage}", _deviceIdentifier, ex.Message);
}
finally
{
lock (_heartbeatLock)
{
_isRunning = false;
}
}
}
/// <summary>
/// 立即发送一次心跳
/// </summary>
public async Task SendHeartbeatImmediatelyAsync()
{
try
{
await _heartbeatAction();
RecordHeartbeatSuccess();
Log.Information("{DeviceId} 已立即发送心跳", _deviceIdentifier);
}
catch (Exception ex)
{
Log.Error(ex, "{DeviceId} 立即发送心跳时发生错误: {ErrorMessage}",
_deviceIdentifier, ex.Message);
throw;
}
}
}
}