216 lines
7.1 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |