Sic.Framework-Nanjing-Baishi/MECF.Framework.Common/Aitex/Core/RT/DataCollection/HighPerformance/DataTraceDataTableCache.cs

148 lines
5.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Aitex.Core.RT.DataCollection.HighPerformance
{
internal class DataTraceDataTableCache : IDataTraceCache
{
#region Variables
private readonly object _syncRoot = new();
private readonly int _maxRowsPerFlush;
private readonly string _tableName;
private DataTable _dataTable;
private readonly Queue<long> _qCachedTimestamps = new();
private readonly int _minCachePeriodMs;
private readonly Stopwatch _stopwatch = new();
private readonly StringBuilder _sqlExpr = new(5000000); // preallocate 5MB
private readonly StringBuilder _sqlValuesList = new(5000000); // preallocate 5MB
private int _lastPersistRows = 0;
private double _lastCachePeriodMs = 0;
#endregion
#region Constructors
/// <summary>
/// 创建数据记录器缓存对象。
/// </summary>
/// <param name="tableName">当前Cache对应的数据库表名称。</param>
/// <param name="module">当前Cache对应的分组名称。</param>
/// <param name="dataHolders">数据获取器对象集合保存当前Cache需要缓存的数据源。</param>
/// <param name="minCachePeriodMs">最小缓存周期,如果外部频繁调用<see cref="Cache"/>方法请求缓存数据,该周期内的重复请求被忽略,以避免高频缓存导致性能下降。</param>
/// <param name="maxRowsPerFlush">每次持久化的最大行数,避免一次性对数据库写入太多数据造成性能问题。</param>
/// <exception cref="ArgumentException"></exception>
public DataTraceDataTableCache(string tableName, string module, IReadOnlyList<IDataBuffer> dataHolders, int minCachePeriodMs = 50, int maxRowsPerFlush = 2000)
{
Debug.Assert(dataHolders is { Count: > 0 }, "Incorrect Data Collector List.");
Category = module;
DataBuffers = dataHolders;
_tableName = tableName;
_maxRowsPerFlush = maxRowsPerFlush;
_minCachePeriodMs = minCachePeriodMs;
// 必须传入有效的数据收集器列表。
if (dataHolders == null || dataHolders.Count == 0)
throw new ArgumentException("数据收集器列表不能为空。", nameof(dataHolders));
// 检查数据收集器列表中是否存在重复的序号。
if (dataHolders.GroupBy(dc => dc.Index).Any(g => g.Count() > 1))
throw new ArgumentException("数据收集器列表中存在重复的序号。", nameof(dataHolders));
// 检查数据收集器列表中是否存在重复的名称。
if (dataHolders.GroupBy(dc => dc.Name).Any(g => g.Count() > 1))
throw new ArgumentException("数据收集器列表中存在重复的名称。", nameof(dataHolders));
// 检查数据收集器列表中是否存在空的获取器。
if (dataHolders.Any(dc => dc.Read == null))
throw new ArgumentException("数据收集器列表中存在空的获取器。", nameof(dataHolders));
_dataTable = CreateDataTable();
/*DATA.Subscribe($"{ModuleName.System}.DBRC.{Module}.CachedCount", ()=> _qCachedTimestamps.Count, SubscriptionAttribute.FLAG.IgnoreSaveDB);
DATA.Subscribe($"{ModuleName.System}.DBRC.{Module}.PersistRows", ()=> _lastPersistRows, SubscriptionAttribute.FLAG.IgnoreSaveDB);
DATA.Subscribe($"{ModuleName.System}.DBRC.{Module}.PersistPeriodMs", ()=> _lastPersistPeriodMs, SubscriptionAttribute.FLAG.IgnoreSaveDB);*/
}
#endregion
#region Properties
/// <summary>
/// 返回当前缓存服务的模组名称。
/// </summary>
public string Category { get; }
/// <summary>
/// 返回数据获取器列表。
/// </summary>
public IReadOnlyList<IDataBuffer> DataBuffers { get; }
#endregion
#region Methods
/// <summary>
/// 创建临时数据表。
/// </summary>
/// <returns></returns>
private DataTable CreateDataTable()
{
lock (_syncRoot)
{
var dt = new DataTable(_tableName);
dt.Columns.Add("time", typeof(long));
foreach (var dh in DataBuffers)
dt.Columns.Add(dh.Name, dh.ValueType);
return dt;
}
}
/// <summary>
/// 立即缓存当前数据。
/// </summary>
public void Cache()
{
lock (_syncRoot)
{
// 限制最大Cache频率
if (_stopwatch.IsRunning && _stopwatch.ElapsedMilliseconds < _minCachePeriodMs)
return;
//TODO 需要限制Q的最大数据量并且检测HalfFull和Full事件并触发警告可能PC性能出现问题
_lastCachePeriodMs = _stopwatch.ElapsedMilliseconds;
_stopwatch.Restart();
var ts = DateTime.Now.Ticks;
var row = _dataTable.NewRow();
row["time"] = ts;
var ret = Parallel.ForEach(DataBuffers, dc =>
{
var ret = dc.Read();
row[dc.Name] = ret;
});
if (ret.IsCompleted)
{
_dataTable.Rows.Add(row);
}
}
}
#endregion
}
}