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 _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 /// /// 创建数据记录器缓存对象。 /// /// 当前Cache对应的数据库表名称。 /// 当前Cache对应的分组名称。 /// 数据获取器对象集合,保存当前Cache需要缓存的数据源。 /// 最小缓存周期,如果外部频繁调用方法请求缓存数据,该周期内的重复请求被忽略,以避免高频缓存导致性能下降。 /// 每次持久化的最大行数,避免一次性对数据库写入太多数据造成性能问题。 /// public DataTraceDataTableCache(string tableName, string module, IReadOnlyList 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 /// /// 返回当前缓存服务的模组名称。 /// public string Category { get; } /// /// 返回数据获取器列表。 /// public IReadOnlyList DataBuffers { get; } #endregion #region Methods /// /// 创建临时数据表。 /// /// 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; } } /// /// 立即缓存当前数据。 /// 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 } }