mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 07:33:58 +08:00 
			
		
		
		
	Compare commits
	
		
			13 Commits
		
	
	
		
			10.11.115.
			...
			10.12.4.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 96b4287f3a | ||
|   | 7d406de29f | ||
|   | 81f0ef466a | ||
|   | 3f2d6b133c | ||
|   | e776dc67eb | ||
| ![devin-ai-integration[bot]](/assets/img/avatar_default.png)  | bc5827d140 | ||
|   | 21838bf4af | ||
|   | 6090108597 | ||
|   | b47b9e6f43 | ||
|   | 18d1cffb2d | ||
|   | 516fd7f235 | ||
|   | 2ee16c3533 | ||
|   | 7d22f5c78e | 
| @@ -19,12 +19,14 @@ | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all"> | ||||
| 		  <IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| 		</PackageReference> | ||||
| 		<PackageReference Include="Rougamo.Fody" Version="5.0.2" /> | ||||
| 	</ItemGroup> | ||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> | ||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="8.0.2" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="9.0.10" /> | ||||
| 		<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" /> | ||||
|  | ||||
| 	</ItemGroup> | ||||
|   | ||||
| @@ -92,7 +92,8 @@ public class Startup : AppStartup | ||||
|              options.RootComponents.MaxJSRootComponents = 500; | ||||
|              options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); | ||||
|              options.MaxBufferedUnacknowledgedRenderBatches = 20; | ||||
|              options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); | ||||
|              options.DisconnectedCircuitMaxRetained = 1; | ||||
|              options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10); | ||||
|          }) | ||||
|          .AddHubOptions(options => | ||||
|          { | ||||
| @@ -103,6 +104,7 @@ public class Startup : AppStartup | ||||
|              options.ClientTimeoutInterval = TimeSpan.FromMinutes(2); | ||||
|              options.KeepAliveInterval = TimeSpan.FromSeconds(15); | ||||
|              options.HandshakeTimeout = TimeSpan.FromSeconds(30); | ||||
|  | ||||
|          }); | ||||
|  | ||||
| #else | ||||
| @@ -112,7 +114,8 @@ public class Startup : AppStartup | ||||
|             options.RootComponents.MaxJSRootComponents = 500; | ||||
|             options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); | ||||
|             options.MaxBufferedUnacknowledgedRenderBatches = 20; | ||||
|             options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); | ||||
|              options.DisconnectedCircuitMaxRetained = 1; | ||||
|              options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10); | ||||
|         }).AddHubOptions(options => | ||||
|         { | ||||
|             //单个传入集线器消息的最大大小。默认 32 KB | ||||
|   | ||||
| @@ -15,14 +15,10 @@ | ||||
| 		<PublishReadyToRunComposite>true</PublishReadyToRunComposite> | ||||
| 		<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon> | ||||
|  | ||||
| 		<CETCompat>false</CETCompat> | ||||
| 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
| 		<CETCompat>false</CETCompat> | ||||
| 		<!--使用自托管线程池--> | ||||
| 		<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> --> | ||||
|  | ||||
| 		<!--使用工作站GC--> | ||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> | ||||
| 		 | ||||
| 		<!--<PlatformTarget>x86</PlatformTarget>--> | ||||
| 	</PropertyGroup> | ||||
|   | ||||
| @@ -27,7 +27,7 @@ internal class CacheManager | ||||
| { | ||||
|     private IMemoryCache Cache { get; set; } | ||||
|  | ||||
|     private IServiceProvider Provider { get; set; } | ||||
|     private static IServiceProvider Provider => App.RootServices; | ||||
|  | ||||
|     [NotNull] | ||||
|     private static CacheManager? Instance { get; set; } | ||||
| @@ -40,8 +40,7 @@ internal class CacheManager | ||||
|     static CacheManager() | ||||
|     { | ||||
|         Instance = new(); | ||||
|         Instance.Provider = App.RootServices; | ||||
|         Instance.Cache = Instance.Provider.GetRequiredService<IMemoryCache>(); | ||||
|         Instance.Cache = Provider.GetRequiredService<IMemoryCache>(); | ||||
|         Options = App.RootServices.GetRequiredService<IOptions<BootstrapBlazorOptions>>().Value; | ||||
|     } | ||||
|  | ||||
| @@ -236,7 +235,7 @@ internal class CacheManager | ||||
|     /// <returns></returns> | ||||
|     public static IStringLocalizer? CreateLocalizerByType(Type resourceSource) => resourceSource.Assembly.IsDynamic | ||||
|         ? null | ||||
|         : Instance.Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource); | ||||
|         : Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获得 <see cref="JsonLocalizationOptions"/> 值 | ||||
| @@ -244,7 +243,7 @@ internal class CacheManager | ||||
|     /// <returns></returns> | ||||
|     private static JsonLocalizationOptions GetJsonLocalizationOption() | ||||
|     { | ||||
|         var localizationOptions = Instance.Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>(); | ||||
|         var localizationOptions = Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>(); | ||||
|         return localizationOptions.Value; | ||||
|     } | ||||
|     /// <summary> | ||||
| @@ -253,7 +252,7 @@ internal class CacheManager | ||||
|     /// <returns></returns> | ||||
|     private static BootstrapBlazorOptions GetBootstrapBlazorOption() | ||||
|     { | ||||
|         var localizationOptions = Instance.Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>(); | ||||
|         var localizationOptions = Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>(); | ||||
|         return localizationOptions.Value; | ||||
|     } | ||||
|     /// <summary> | ||||
| @@ -269,7 +268,7 @@ internal class CacheManager | ||||
|             return null; | ||||
|         } | ||||
|         IStringLocalizer? ret = null; | ||||
|         var factories = Instance.Provider.GetServices<IStringLocalizerFactory>(); | ||||
|         var factories = Provider.GetServices<IStringLocalizerFactory>(); | ||||
|         var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory); | ||||
|         if (factory != null) | ||||
|         { | ||||
| @@ -345,7 +344,7 @@ internal class CacheManager | ||||
|     /// <param name="typeName"></param> | ||||
|     /// <param name="includeParentCultures"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Instance.Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures); | ||||
|     public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures); | ||||
|     #endregion | ||||
|  | ||||
|     #region DisplayName | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" /> | ||||
| 		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> | ||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.11.2" /> | ||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.11.4" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -25,17 +25,11 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class | ||||
|     /// <summary>最大个数。默认0,0表示无上限</summary> | ||||
|     public Int32 Max { get; set; } = 0; | ||||
|  | ||||
|     /// <summary>最小个数。默认1</summary> | ||||
|     public Int32 Min { get; set; } = 1; | ||||
|  | ||||
|     private readonly object _syncRoot = new(); | ||||
|  | ||||
|     /// <summary>基础空闲集合。只保存最小个数,最热部分</summary> | ||||
|     private readonly Stack<T> _free = new(); | ||||
|  | ||||
|     /// <summary>扩展空闲集合。保存最小个数以外部分</summary> | ||||
|     private readonly Queue<T> _free2 = new(); | ||||
|  | ||||
|     /// <summary>借出去的放在这</summary> | ||||
|     private readonly HashSet<T> _busy = new(); | ||||
|  | ||||
| @@ -79,7 +73,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class | ||||
|             if (_inited) return; | ||||
|             _inited = true; | ||||
|  | ||||
|             WriteLog($"Init {typeof(T).FullName} Min={Min} Max={Max}"); | ||||
|             WriteLog($"Init {typeof(T).FullName} Max={Max}"); | ||||
|         } | ||||
|     } | ||||
|     #endregion | ||||
| @@ -99,26 +93,20 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class | ||||
|                     pi = _free.Pop(); | ||||
|                     _FreeCount--; | ||||
|                 } | ||||
|                 else if (_free2.Count > 0) | ||||
|                 { | ||||
|                     pi = _free2.Dequeue(); | ||||
|                     _FreeCount--; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     var count = BusyCount; | ||||
|                     if (Max > 0 && count >= Max) | ||||
|                     if (Max > 0 && BusyCount >= Max) | ||||
|                     { | ||||
|                         var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}"; | ||||
|                         var msg = $"申请失败,已有 {BusyCount:n0} 达到或超过最大值 {Max:n0}"; | ||||
|                         WriteLog("Acquire Max " + msg); | ||||
|                         throw new Exception(Name + " " + msg); | ||||
|                     } | ||||
|  | ||||
|                     pi = OnCreate(); | ||||
|                     if (count == 0) Init(); | ||||
|                     if (BusyCount == 0) Init(); | ||||
|  | ||||
| #if DEBUG | ||||
|                     WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, count + 1); | ||||
|                     WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, BusyCount + 1); | ||||
| #endif | ||||
|                 } | ||||
|             } | ||||
| @@ -177,10 +165,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class | ||||
|         } | ||||
|         lock (_syncRoot) | ||||
|         { | ||||
|             if (_FreeCount < Min) | ||||
|             _free.Push(value); | ||||
|             else | ||||
|                 _free2.Enqueue(value); | ||||
|             _FreeCount++; | ||||
|         } | ||||
|  | ||||
| @@ -214,12 +199,6 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class | ||||
|                 OnDispose(pi); | ||||
|             } | ||||
|  | ||||
|             while (_free2.Count > 0) | ||||
|             { | ||||
|                 var pi = _free2.Dequeue(); | ||||
|                 OnDispose(pi); | ||||
|             } | ||||
|  | ||||
|             _FreeCount = 0; | ||||
|  | ||||
|             foreach (var item in _busy) | ||||
|   | ||||
| @@ -8,8 +8,6 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using ThingsGateway.NewLife.Collections; | ||||
|  | ||||
| namespace ThingsGateway.NewLife; | ||||
|  | ||||
| /// <summary> | ||||
|   | ||||
| @@ -61,7 +61,8 @@ public class TextFileLog : Logger, IDisposable | ||||
|         MaxBytes = set.LogFileMaxBytes; | ||||
|         Backups = set.LogFileBackups; | ||||
|  | ||||
|         _Timer = new TimerX(DoWriteAndClose, null, 0_000, 5_000) { Async = true }; | ||||
|         _Timer = new TimerX(DoWriteAndClose, null, 0_000, 60_000, nameof(TextFileLog)) { Async = true }; | ||||
|         _WriteTimer = new TimerX(DoWrite, null, 0_000, 1000, nameof(TextFileLog)) { Async = true }; | ||||
|     } | ||||
|  | ||||
|     private static readonly NonBlockingDictionary<String, TextFileLog> cache = new(StringComparer.OrdinalIgnoreCase); | ||||
| @@ -96,6 +97,7 @@ public class TextFileLog : Logger, IDisposable | ||||
|     protected virtual void Dispose(Boolean disposing) | ||||
|     { | ||||
|         _Timer.TryDispose(); | ||||
|         _WriteTimer.TryDispose(); | ||||
|  | ||||
|         // 销毁前把队列日志输出 | ||||
|         if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0) WriteAndClose(DateTime.MinValue); | ||||
| @@ -147,35 +149,72 @@ public class TextFileLog : Logger, IDisposable | ||||
|  | ||||
|     /// <summary>获取日志文件路径</summary> | ||||
|     /// <returns></returns> | ||||
|     private String? GetLogFile() | ||||
|     private string? GetLogFile() | ||||
|     { | ||||
|         // 单日志文件 | ||||
|         if (_isFile) return LogPath.GetBasePath(); | ||||
|  | ||||
|         // 目录多日志文件 | ||||
|         var logfile = LogPath.CombinePath(String.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level)).GetBasePath(); | ||||
|         var baseFile = LogPath.CombinePath( | ||||
|             string.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level) | ||||
|         ).GetBasePath(); | ||||
|  | ||||
|         // 是否限制文件大小 | ||||
|         if (MaxBytes == 0) return logfile; | ||||
|         // 不限制大小 | ||||
|         if (MaxBytes == 0) return baseFile; | ||||
|  | ||||
|         // 找到今天第一个未达到最大上限的文件 | ||||
|         var max = MaxBytes * 1024L * 1024L; | ||||
|         var ext = Path.GetExtension(logfile); | ||||
|         var name = logfile.TrimEnd(ext); | ||||
|         var ext = Path.GetExtension(FileFormat); | ||||
|  | ||||
|         string? latestFile = null; | ||||
|         DateTime latestTime = DateTime.MinValue; | ||||
|  | ||||
|         foreach (var path in Directory.EnumerateFiles(LogPath, $"*{ext}", SearchOption.TopDirectoryOnly)) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var lastWrite = File.GetLastWriteTimeUtc(path); | ||||
|                 if (lastWrite > latestTime) | ||||
|                 { | ||||
|                     latestTime = lastWrite; | ||||
|                     latestFile = path; | ||||
|                 } | ||||
|             } | ||||
|             catch { } | ||||
|         } | ||||
|  | ||||
|         if (latestFile != null) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var len = new FileInfo(latestFile).Length; | ||||
|                 if (len < max) | ||||
|                     return latestFile; | ||||
|             } | ||||
|             catch { } | ||||
|         } | ||||
|  | ||||
|         var fileNameWithoutExt = Path.Combine( | ||||
|             Path.GetDirectoryName(baseFile)!, | ||||
|             Path.GetFileNameWithoutExtension(baseFile) | ||||
|         ); | ||||
|  | ||||
|         // 依序找下一个可用文件 | ||||
|         for (var i = 1; i < 1024; i++) | ||||
|         { | ||||
|             if (i > 1) logfile = $"{name}_{i}{ext}"; | ||||
|             var nextFile = i == 1 ? $"{fileNameWithoutExt}{ext}" : $"{fileNameWithoutExt}_{i}{ext}"; | ||||
|             if (!File.Exists(nextFile)) | ||||
|                 return nextFile; | ||||
|  | ||||
|             var fi = logfile.AsFile(); | ||||
|             if (!fi.Exists || fi.Length < max) return logfile; | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region 异步写日志 | ||||
|     private readonly TimerX? _Timer; | ||||
|     private readonly TimerX? _WriteTimer; | ||||
|     private readonly ConcurrentQueue<String> _Logs = new(); | ||||
|     private volatile Int32 _logCount; | ||||
|     private Int32 _writing; | ||||
| @@ -186,9 +225,9 @@ public class TextFileLog : Logger, IDisposable | ||||
|     { | ||||
|         var writer = LogWriter; | ||||
|  | ||||
|         var now = TimerX.Now.AddHours(Setting.Current.UtcIntervalHours); | ||||
|         var logFile = GetLogFile(); | ||||
|         if (logFile.IsNullOrEmpty()) return; | ||||
|         var now = TimerX.Now.AddHours(Setting.Current.UtcIntervalHours); | ||||
|  | ||||
|         if (!_isFile && logFile != CurrentLogFile) | ||||
|         { | ||||
| @@ -223,7 +262,10 @@ public class TextFileLog : Logger, IDisposable | ||||
|         // 连续5秒没日志,就关闭 | ||||
|         _NextClose = now.AddSeconds(5); | ||||
|     } | ||||
|  | ||||
|     private void DoWrite(Object? state) | ||||
|     { | ||||
|         WriteLog(); | ||||
|     } | ||||
|     /// <summary>关闭文件</summary> | ||||
|     private void DoWriteAndClose(Object? state) | ||||
|     { | ||||
| @@ -240,24 +282,18 @@ public class TextFileLog : Logger, IDisposable | ||||
|                 // 删除 *.del | ||||
|                 try | ||||
|                 { | ||||
|                     var dels = di.GetFiles("*.del"); | ||||
|                     if (dels?.Length > 0) | ||||
|                     { | ||||
|                         foreach (var item in dels) | ||||
|                     foreach (var item in di.EnumerateFiles("*.del")) | ||||
|                     { | ||||
|                         item.Delete(); | ||||
|                     } | ||||
|                 } | ||||
|                 } | ||||
|                 catch { } | ||||
|  | ||||
|                 var ext = Path.GetExtension(FileFormat); | ||||
|                 var fis = di.GetFiles("*" + ext); | ||||
|                 if (fis != null && fis.Length > Backups) | ||||
|                 { | ||||
|                     // 删除最旧的文件 | ||||
|                     var retain = fis.Length - Backups; | ||||
|                     fis = fis.OrderBy(e => e.LastWriteTimeUtc).Take(retain).ToArray(); | ||||
|                 var fis = di.EnumerateFiles($"*{ext}") | ||||
|                            .OrderByDescending(e => e.LastWriteTimeUtc) | ||||
|                            .Skip(Backups); | ||||
|  | ||||
|                 foreach (var item in fis) | ||||
|                 { | ||||
|                     OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length); | ||||
| @@ -279,7 +315,6 @@ public class TextFileLog : Logger, IDisposable | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     } | ||||
|  | ||||
|     /// <summary>写入队列日志并关闭文件</summary> | ||||
|     protected virtual void WriteAndClose(DateTime closeTime) | ||||
| @@ -323,7 +358,6 @@ public class TextFileLog : Logger, IDisposable | ||||
|         // 推入队列 | ||||
|         Enqueue($"{e.GetAndReset()}"); | ||||
|  | ||||
|         WriteLog(); | ||||
|     } | ||||
|  | ||||
|     protected bool Check() | ||||
| @@ -340,37 +374,19 @@ public class TextFileLog : Logger, IDisposable | ||||
|     } | ||||
|     protected void WriteLog() | ||||
|     { | ||||
|         // 异步写日志,实时。即使这里错误,定时器那边仍然会补上 | ||||
|         // 写日志,实时。即使这里错误,定时器那边仍然会补上 | ||||
|         if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0) | ||||
|         { | ||||
|             // 调试级别 或 致命错误 同步写日志 | ||||
|             if (Setting.Current.LogLevel <= LogLevel.Debug || Level >= LogLevel.Error) | ||||
|             { | ||||
|             try | ||||
|             { | ||||
|                     WriteFile(); | ||||
|                 if (!_Logs.IsEmpty) WriteFile(); | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 _writing = 0; | ||||
|             } | ||||
|         } | ||||
|             else | ||||
|             { | ||||
|                 ThreadPool.UnsafeQueueUserWorkItem(s => | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         WriteFile(); | ||||
|                     } | ||||
|                     catch { } | ||||
|                     finally | ||||
|                     { | ||||
|                         _writing = 0; | ||||
|                     } | ||||
|                 }, null); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     #endregion | ||||
|  | ||||
|   | ||||
| @@ -80,7 +80,14 @@ public static class XTrace | ||||
|  | ||||
|         Log.Error("{0}", ex); | ||||
|     } | ||||
|     public static void WriteException(Exception ex, string message) | ||||
|     { | ||||
|         if (!InitLog()) return; | ||||
|  | ||||
|         WriteVersion(); | ||||
|  | ||||
|         Log.Error("{0}, {1}", message, ex); | ||||
|     } | ||||
|     #endregion 写日志 | ||||
|  | ||||
|     #region 构造 | ||||
|   | ||||
| @@ -12,18 +12,13 @@ namespace PooledAwait | ||||
|     { | ||||
|         private static ObjectPoolLock<T> pool = new(); | ||||
|  | ||||
|         [ThreadStatic] | ||||
|         private static T? ts_local; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets an instance from the pool if possible | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static T? TryGet() | ||||
|         { | ||||
|             var tmp = ts_local; | ||||
|             ts_local = null; | ||||
|             return tmp ?? pool.Get(); | ||||
|             return pool.Get(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
| @@ -34,11 +29,6 @@ namespace PooledAwait | ||||
|         { | ||||
|             if (value != null) | ||||
|             { | ||||
|                 if (ts_local == null) | ||||
|                 { | ||||
|                     ts_local = value; | ||||
|                     return; | ||||
|                 } | ||||
|                 pool.Return(value); | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -65,8 +65,21 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|     public static TimeProvider GlobalTimeProvider { get; set; } = TimeProvider.System; | ||||
|     #endregion | ||||
|     #region 构造 | ||||
|     private TimerScheduler(String name) => Name = name; | ||||
|  | ||||
|     private TimerScheduler(String name) | ||||
|     { | ||||
|         Name = name; | ||||
|         _processCallback = state => | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 Execute(state); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 XTrace.WriteException(ex, "Timer执行错误"); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|     /// <summary>销毁</summary> | ||||
|     public void Dispose() | ||||
|     { | ||||
| @@ -258,17 +271,7 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|                         else | ||||
|                             //Task.Factory.StartNew(() => ProcessItem(timer)); | ||||
|                             // 不需要上下文流动,捕获所有异常 | ||||
|                             ThreadPool.UnsafeQueueUserWorkItem(s => | ||||
|                             { | ||||
|                                 try | ||||
|                                 { | ||||
|                                     Execute(s); | ||||
|                                 } | ||||
|                                 catch (Exception ex) | ||||
|                                 { | ||||
|                                     XTrace.WriteException(ex); | ||||
|                                 } | ||||
|                             }, timer); | ||||
|                             ThreadPool.UnsafeQueueUserWorkItem(_processCallback, timer); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -283,7 +286,7 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|  | ||||
|         WriteLog("调度线程已退出:{0}", Name); | ||||
|     } | ||||
|  | ||||
|     private readonly WaitCallback _processCallback; | ||||
|     /// <summary>检查定时器是否到期</summary> | ||||
|     /// <param name="timer"></param> | ||||
|     /// <param name="now"></param> | ||||
| @@ -325,9 +328,9 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|  | ||||
|         timer.hasSetNext = false; | ||||
|  | ||||
|         string tracerName = timer.TracerName ?? "timer:ExecuteAsync"; | ||||
|         string timerArg = timer.Timers.ToString(); | ||||
|         using var span = timer.Tracer?.NewSpan(tracerName, timerArg); | ||||
|         //string tracerName = timer.TracerName ?? "timer:ExecuteAsync"; | ||||
|         //string timerArg = timer.Timers.ToString(); | ||||
|         //using var span = timer.Tracer?.NewSpan(tracerName, timerArg); | ||||
|         var sw = ValueStopwatch.StartNew(); | ||||
|         try | ||||
|         { | ||||
| @@ -351,7 +354,7 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|         // 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事 | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             span?.SetError(ex, null); | ||||
|             //span?.SetError(ex, null); | ||||
|             XTrace.WriteException(ex); | ||||
|         } | ||||
|         finally | ||||
| @@ -377,9 +380,9 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|  | ||||
|             timer.hasSetNext = false; | ||||
|  | ||||
|             string tracerName = timer.TracerName ?? "timer:ExecuteAsync"; | ||||
|             string timerArg = timer.Timers.ToString(); | ||||
|             using var span = timer.Tracer?.NewSpan(tracerName, timerArg); | ||||
|             //string tracerName = timer.TracerName ?? "timer:ExecuteAsync"; | ||||
|             //string timerArg = timer.Timers.ToString(); | ||||
|             //using var span = timer.Tracer?.NewSpan(tracerName, timerArg); | ||||
|             var sw = ValueStopwatch.StartNew(); | ||||
|             try | ||||
|             { | ||||
| @@ -427,7 +430,7 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|             // 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事 | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 span?.SetError(ex, null); | ||||
|                 //span?.SetError(ex, null); | ||||
|                 XTrace.WriteException(ex); | ||||
|             } | ||||
|             finally | ||||
|   | ||||
| @@ -37,7 +37,7 @@ | ||||
| 		<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.2" /> | ||||
| 		<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" /> | ||||
| 		<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="8.0.2" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="9.0.10" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| <Project> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<PluginVersion>10.11.115</PluginVersion> | ||||
| 		<ProPluginVersion>10.11.115</ProPluginVersion> | ||||
| 		<DefaultVersion>10.11.115</DefaultVersion> | ||||
| 		<PluginVersion>10.12.4</PluginVersion> | ||||
| 		<ProPluginVersion>10.12.4</ProPluginVersion> | ||||
| 		<DefaultVersion>10.12.4</DefaultVersion> | ||||
| 		<AuthenticationVersion>10.11.6</AuthenticationVersion> | ||||
| 		<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion> | ||||
| 		<NET8Version>8.0.21</NET8Version> | ||||
|   | ||||
| @@ -18,19 +18,19 @@ | ||||
|             { | ||||
|                 <Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select> | ||||
|             } | ||||
|         <Tooltip class=" col-auto" Title="@RazorLocalizer[Pause?"Play":"Pause"]" Placement="Placement.Bottom"> | ||||
|             <Tooltip class=" col-auto" Title=@(Pause? PlayText:PauseText) Placement="Placement.Bottom"> | ||||
|  | ||||
|                 <Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(Pause ? "fa fa-play" : "fa fa-pause") OnClick="OnPause" /> | ||||
|  | ||||
|             </Tooltip> | ||||
|  | ||||
|         <Tooltip class=" col-auto" Title="@RazorLocalizer["Export"]" Placement="Placement.Bottom"> | ||||
|             <Tooltip class=" col-auto" Title="@ExportText" Placement="Placement.Bottom"> | ||||
|  | ||||
|                 <Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" /> | ||||
|  | ||||
|             </Tooltip> | ||||
|  | ||||
|         <Tooltip class=" col-auto" Title="@RazorLocalizer["Delete"]" Placement="Placement.Bottom"> | ||||
|             <Tooltip class=" col-auto" Title="@DeleteText" Placement="Placement.Bottom"> | ||||
|  | ||||
|                 <Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" /> | ||||
|  | ||||
|   | ||||
| @@ -10,12 +10,11 @@ | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Web; | ||||
|  | ||||
| using System.Diagnostics; | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.Foundation; | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Threading; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| @@ -23,6 +22,24 @@ namespace ThingsGateway.Debug; | ||||
|  | ||||
| public partial class LogConsole : IDisposable | ||||
| { | ||||
|  | ||||
|     private string PlayText { get; set; } | ||||
|     private string PauseText { get; set; } | ||||
|     private string ExportText { get; set; } | ||||
|     private string DeleteText { get; set; } | ||||
|  | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         PlayText = RazorLocalizer["Play"]; | ||||
|         PauseText = RazorLocalizer["Pause"]; | ||||
|         ExportText = RazorLocalizer["Export"]; | ||||
|         DeleteText = RazorLocalizer["Delete"]; | ||||
|  | ||||
|         _Timer = new TimerX(RunTimerAsync, null, 1_000, 1_000, nameof(LogConsole)) { Async = true }; | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|     private TimerX _Timer; | ||||
|  | ||||
|     private bool Pause; | ||||
|  | ||||
|     public bool Disposed { get; set; } | ||||
| @@ -69,7 +86,7 @@ public partial class LogConsole : IDisposable | ||||
|         { | ||||
|             logPath = LogPath; | ||||
|             Messages = new List<LogMessage>(); | ||||
|             await ExecuteAsync(); | ||||
|             _Timer?.SetNext(0); | ||||
|         } | ||||
|  | ||||
|         await base.OnParametersSetAsync(); | ||||
| @@ -82,16 +99,11 @@ public partial class LogConsole : IDisposable | ||||
|     public void Dispose() | ||||
|     { | ||||
|         Disposed = true; | ||||
|         _Timer?.SafeDispose(); | ||||
|         GC.SuppressFinalize(this); | ||||
|     } | ||||
|     private WaitLock WaitLock = new(nameof(LogConsole)); | ||||
|     protected async Task ExecuteAsync() | ||||
|     protected async ValueTask ExecuteAsync() | ||||
|     { | ||||
|         if (WaitLock.Waited) return; | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync(); | ||||
|             await Task.Delay(1000); | ||||
|  | ||||
|         if (LogPath != null) | ||||
|         { | ||||
| @@ -103,43 +115,23 @@ public partial class LogConsole : IDisposable | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                     await Task.Run(async () => | ||||
|                     { | ||||
|                         Stopwatch sw = Stopwatch.StartNew(); | ||||
|  | ||||
|                 var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault()); | ||||
|                 if (result.IsSuccess) | ||||
|                 { | ||||
|                             Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList(); | ||||
|                     Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToArray(); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                             Messages = new List<LogMessage>(); | ||||
|                     Messages = Array.Empty<LogMessage>(); | ||||
|                 } | ||||
|                         sw.Stop(); | ||||
|                         if (sw.ElapsedMilliseconds > 500) | ||||
|                         { | ||||
|                             await Task.Delay(1000); | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             NewLife.Log.XTrace.WriteException(ex); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             WaitLock.Release(); | ||||
|  | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         _ = RunTimerAsync(); | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private async Task Delete() | ||||
|     { | ||||
|         await TextFileReadService.DeleteLogDataAsync(LogPath); | ||||
| @@ -185,19 +177,9 @@ public partial class LogConsole : IDisposable | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
|  | ||||
|     private async Task RunTimerAsync() | ||||
|     { | ||||
|         while (!Disposed) | ||||
|         { | ||||
|             try | ||||
|     private async Task RunTimerAsync(object? state) | ||||
|     { | ||||
|         await ExecuteAsync(); | ||||
|         await InvokeAsync(StateHasChanged); | ||||
|     } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 NewLife.Log.XTrace.WriteException(ex); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -8,6 +8,8 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using PooledAwait; | ||||
|  | ||||
| using ThingsGateway.Foundation.Extension.String; | ||||
|  | ||||
| using TouchSocket.SerialPorts; | ||||
| @@ -26,65 +28,33 @@ public static class ChannelOptionsExtensions | ||||
|     /// <param name="e">接收数据</param> | ||||
|     /// <param name="funcs">事件</param> | ||||
|     /// <returns></returns> | ||||
|     internal static ValueTask OnChannelReceivedEvent( | ||||
|     this IClientChannel clientChannel, | ||||
|     ReceivedDataEventArgs e, | ||||
|     ChannelReceivedEventHandler funcs) | ||||
|     internal static ValueTask OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs) | ||||
|     { | ||||
|         clientChannel.ThrowIfNull(nameof(IClientChannel)); | ||||
|         e.ThrowIfNull(nameof(ReceivedDataEventArgs)); | ||||
|         funcs.ThrowIfNull(nameof(ChannelReceivedEventHandler)); | ||||
|  | ||||
|         if (funcs.Count == 0) return EasyValueTask.CompletedTask; | ||||
|         return OnChannelReceivedEvent(clientChannel, e, funcs); | ||||
|  | ||||
|         return InvokeHandlersSequentially(clientChannel, e, funcs); | ||||
|         static async PooledValueTask OnChannelReceivedEvent(IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs) | ||||
|         { | ||||
|             if (funcs.Count > 0) | ||||
|             { | ||||
|                 for (int i = 0; i < funcs.Count; i++) | ||||
|                 { | ||||
|                     var func = funcs[i]; | ||||
|                     if (func == null) continue; | ||||
|                     var taskResult = func.Invoke(clientChannel, e, i == funcs.Count - 1); | ||||
|                     if (!taskResult.IsCompletedSuccessfully) | ||||
|                     { | ||||
|                         await taskResult.ConfigureAwait(false); | ||||
|                     } | ||||
|  | ||||
|     private static ValueTask InvokeHandlersSequentially( | ||||
|         IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs) | ||||
|                     if (e.Handled) | ||||
|                     { | ||||
|         var enumerator = new HandlerEnumerator(clientChannel, e, funcs); | ||||
|         return enumerator.MoveNextAsync(); | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|     private struct HandlerEnumerator | ||||
|     { | ||||
|         private readonly IClientChannel _channel; | ||||
|         private readonly ReceivedDataEventArgs _e; | ||||
|         private readonly ChannelReceivedEventHandler _funcs; | ||||
|         private int _index; | ||||
|  | ||||
|         public HandlerEnumerator(IClientChannel channel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs) | ||||
|         { | ||||
|             _channel = channel; | ||||
|             _e = e; | ||||
|             _funcs = funcs; | ||||
|             _index = -1; | ||||
|                 } | ||||
|  | ||||
|         public ValueTask MoveNextAsync() | ||||
|         { | ||||
|             _index++; | ||||
|             if (_index >= _funcs.Count) return default; | ||||
|  | ||||
|             var func = _funcs[_index]; | ||||
|             if (func == null) return MoveNextAsync(); | ||||
|  | ||||
|             bool isLast = _index == _funcs.Count - 1; | ||||
|             var vt = func.Invoke(_channel, _e, isLast); | ||||
|             if (vt.IsCompletedSuccessfully) | ||||
|             { | ||||
|                 if (_e.Handled) return default; | ||||
|                 return MoveNextAsync(); | ||||
|             } | ||||
|             return Awaited(vt); | ||||
|         } | ||||
|  | ||||
|         private async ValueTask Awaited(ValueTask vt) | ||||
|         { | ||||
|             await vt.ConfigureAwait(false); | ||||
|             if (!_e.Handled) | ||||
|                 await MoveNextAsync().ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -128,7 +128,6 @@ public class TextFileLogger : ThingsGateway.NewLife.Log.TextFileLog, TouchSocket | ||||
|         // 推入队列 | ||||
|         Enqueue(stringBuilder.ToString()); | ||||
|  | ||||
|         WriteLog(); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|   | ||||
| @@ -43,6 +43,16 @@ public struct OperResult<T> : IOperResult<T> | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 从另一个操作对象中赋值信息 | ||||
|     /// </summary> | ||||
|     public OperResult(OperResult operResult) | ||||
|     { | ||||
|         OperCode = operResult.OperCode; | ||||
|         ErrorMessage = operResult.ErrorMessage; | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 传入错误信息 | ||||
| @@ -129,8 +139,15 @@ public struct OperResult<T> : IOperResult<T> | ||||
|     /// <param name="operResult"></param> | ||||
|     public static implicit operator OperResult(OperResult<T> operResult) | ||||
|     { | ||||
|         return new OperResult(operResult); | ||||
|         return new OperResult | ||||
|         { | ||||
|             OperCode = operResult.OperCode, | ||||
|             ErrorMessage = operResult.ErrorMessage, | ||||
|             Exception = operResult.Exception, | ||||
|             ErrorType = operResult.ErrorType | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| /// <inheritdoc/> | ||||
| @@ -162,7 +179,13 @@ public struct OperResult<T, T2> : IOperResult<T, T2> | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|  | ||||
|     public OperResult(OperResult operResult) | ||||
|     { | ||||
|         OperCode = operResult.OperCode; | ||||
|         ErrorMessage = operResult.ErrorMessage; | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 传入错误信息 | ||||
|     /// </summary> | ||||
| @@ -275,7 +298,13 @@ public struct OperResult<T, T2, T3> : IOperResult<T, T2, T3> | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|  | ||||
|     public OperResult(OperResult operResult) | ||||
|     { | ||||
|         OperCode = operResult.OperCode; | ||||
|         ErrorMessage = operResult.ErrorMessage; | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 传入错误信息 | ||||
|     /// </summary> | ||||
| @@ -389,7 +418,13 @@ public struct OperResult : IOperResult | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|  | ||||
|     public OperResult(OperResult operResult) | ||||
|     { | ||||
|         OperCode = operResult.OperCode; | ||||
|         ErrorMessage = operResult.ErrorMessage; | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 传入错误信息 | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -15,7 +15,6 @@ using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
|  | ||||
| using System.ComponentModel; | ||||
| using ThingsGateway.NewLife.DictionaryExtensions; | ||||
|  | ||||
| using ThingsGateway.FriendlyException; | ||||
|  | ||||
|   | ||||
| @@ -380,7 +380,7 @@ public abstract partial class CollectBase : DriverBase | ||||
|             if (cancellationToken.IsCancellationRequested) return; | ||||
|             CancellationToken readToken = default; | ||||
|             var readerLockTask = @this.ReadWriteLock.ReaderLockAsync(cancellationToken); | ||||
|             if (!readerLockTask.IsCompleted) | ||||
|             if (!readerLockTask.IsCompletedSuccessfully) | ||||
|             { | ||||
|                 readToken = await readerLockTask.ConfigureAwait(false); | ||||
|             } | ||||
| @@ -403,7 +403,7 @@ public abstract partial class CollectBase : DriverBase | ||||
|  | ||||
|             OperResult<ReadOnlyMemory<byte>> readResult = default; | ||||
|             var readTask = @this.ReadSourceAsync(variableSourceRead, allToken); | ||||
|             if (!readTask.IsCompleted) | ||||
|             if (!readTask.IsCompletedSuccessfully) | ||||
|             { | ||||
|                 readResult = await readTask.ConfigureAwait(false); | ||||
|             } | ||||
| @@ -435,7 +435,7 @@ public abstract partial class CollectBase : DriverBase | ||||
|                 //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||
|                 //    LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length)); | ||||
|                 var readTask1 = @this.ReadSourceAsync(variableSourceRead, allToken); | ||||
|                 if (!readTask1.IsCompleted) | ||||
|                 if (!readTask1.IsCompletedSuccessfully) | ||||
|                 { | ||||
|                     readResult = await readTask1.ConfigureAwait(false); | ||||
|                 } | ||||
| @@ -490,195 +490,6 @@ public abstract partial class CollectBase : DriverBase | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     //    private ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken) | ||||
|     //    { | ||||
|     //        var enumerator = new ReadVariableSourceEnumerator(this, state, cancellationToken); | ||||
|     //        return enumerator.MoveNextAsync(); | ||||
|     //    } | ||||
|  | ||||
|     //    private struct ReadVariableSourceEnumerator | ||||
|     //    { | ||||
|     //        private readonly CollectBase _owner; | ||||
|     //        private readonly object? _state; | ||||
|     //        private readonly CancellationToken _cancellationToken; | ||||
|  | ||||
|     //        private VariableSourceRead _variableSourceRead; | ||||
|     //        private CancellationToken _readToken; | ||||
|     //        private CancellationToken _allToken; | ||||
|     //        private OperResult<ReadOnlyMemory<byte>> _readResult; | ||||
|     //        private int _readErrorCount; | ||||
|     //        private ValueTask<CancellationToken> _readerLockTask; | ||||
|     //        private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask; | ||||
|     //        private int _step; | ||||
|  | ||||
|     //        public ReadVariableSourceEnumerator(CollectBase owner, object? state, CancellationToken cancellationToken) | ||||
|     //        { | ||||
|     //            _owner = owner; | ||||
|     //            _state = state; | ||||
|     //            _cancellationToken = cancellationToken; | ||||
|  | ||||
|     //            _variableSourceRead = default!; | ||||
|     //            _readToken = default; | ||||
|     //            _allToken = default; | ||||
|     //            _readResult = default; | ||||
|     //            _readErrorCount = 0; | ||||
|     //            _readerLockTask = default; | ||||
|     //            _readTask = default; | ||||
|     //            _step = 0; | ||||
|     //        } | ||||
|  | ||||
|     //        public ValueTask MoveNextAsync() | ||||
|     //        { | ||||
|     //            switch (_step) | ||||
|     //            { | ||||
|     //                case 0: | ||||
|     //                    if (_state is not VariableSourceRead vsr) return default; | ||||
|     //                    _variableSourceRead = vsr; | ||||
|  | ||||
|     //                    if (_owner.Pause) return default; | ||||
|     //                    if (_cancellationToken.IsCancellationRequested) return default; | ||||
|  | ||||
|     //#pragma warning disable CA2012 // 正确使用 ValueTask | ||||
|     //                    _readerLockTask = _owner.ReadWriteLock.ReaderLockAsync(_cancellationToken); | ||||
|     //#pragma warning restore CA2012 // 正确使用 ValueTask | ||||
|     //                    if (!_readerLockTask.IsCompleted) | ||||
|     //                    { | ||||
|     //                        _step = 1; | ||||
|     //                        return AwaitReaderLock(); | ||||
|     //                    } | ||||
|     //                    _readToken = _readerLockTask.Result; | ||||
|     //                    goto case 2; | ||||
|  | ||||
|     //                case 1: | ||||
|     //                    _readToken = _readerLockTask.Result; | ||||
|     //                    goto case 2; | ||||
|  | ||||
|     //                case 2: | ||||
|     //                    if (_readToken.IsCancellationRequested) | ||||
|     //                    { | ||||
|     //                        return _owner.ReadVariableSource(_state, _cancellationToken); | ||||
|     //                    } | ||||
|  | ||||
|     //                    var allTokenSource = _owner._linkedCtsCache.GetLinkedTokenSource(_cancellationToken, _readToken); | ||||
|     //                    _allToken = allTokenSource.Token; | ||||
|  | ||||
|     //#pragma warning disable CA2012 // 正确使用 ValueTask | ||||
|     //                    _readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken); | ||||
|     //#pragma warning restore CA2012 // 正确使用 ValueTask | ||||
|     //                    if (!_readTask.IsCompleted) | ||||
|     //                    { | ||||
|     //                        _step = 3; | ||||
|     //                        return AwaitRead(); | ||||
|     //                    } | ||||
|     //                    _readResult = _readTask.Result; | ||||
|     //                    goto case 4; | ||||
|  | ||||
|     //                case 3: | ||||
|     //                    _readResult = _readTask.Result; | ||||
|     //                    goto case 4; | ||||
|  | ||||
|     //                case 4: | ||||
|     //                    while (!_readResult.IsSuccess && _readErrorCount < _owner.CollectProperties.RetryCount) | ||||
|     //                    { | ||||
|     //                        if (_owner.Pause) return default; | ||||
|     //                        if (_cancellationToken.IsCancellationRequested) return default; | ||||
|  | ||||
|     //                        if (_readToken.IsCancellationRequested) | ||||
|     //                        { | ||||
|     //                            return _owner.ReadVariableSource(_state, _cancellationToken); | ||||
|     //                        } | ||||
|  | ||||
|     //                        _readErrorCount++; | ||||
|     //                        if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||
|     //                            _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}", | ||||
|     //                                _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage)); | ||||
|  | ||||
|     //#pragma warning disable CA2012 // 正确使用 ValueTask | ||||
|     //                        _readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken); | ||||
|     //#pragma warning restore CA2012 // 正确使用 ValueTask | ||||
|     //                        if (!_readTask.IsCompleted) | ||||
|     //                        { | ||||
|     //                            _step = 5; | ||||
|     //                            return AwaitReadRetry(); | ||||
|     //                        } | ||||
|     //                        _readResult = _readTask.Result; | ||||
|     //                    } | ||||
|  | ||||
|     //                    goto case 6; | ||||
|  | ||||
|     //                case 5: | ||||
|     //                    _readResult = _readTask.Result; | ||||
|     //                    _step = 4; | ||||
|     //                    return MoveNextAsync(); | ||||
|  | ||||
|     //                case 6: | ||||
|     //                    if (_readResult.IsSuccess) | ||||
|     //                    { | ||||
|     //                        if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||
|     //                            _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}", | ||||
|     //                                _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.Content.Span.ToHexString(' '))); | ||||
|  | ||||
|     //                        _owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null); | ||||
|     //                    } | ||||
|     //                    else | ||||
|     //                    { | ||||
|     //                        if (_cancellationToken.IsCancellationRequested) return default; | ||||
|     //                        if (_readToken.IsCancellationRequested) | ||||
|     //                        { | ||||
|     //                            return _owner.ReadVariableSource(_state, _cancellationToken); | ||||
|     //                        } | ||||
|  | ||||
|     //                        if (_variableSourceRead.LastErrorMessage != _readResult.ErrorMessage) | ||||
|     //                        { | ||||
|     //                            if (!_cancellationToken.IsCancellationRequested) | ||||
|     //                                _owner.LogMessage?.LogWarning(_readResult.Exception, string.Format(AppResource.CollectFail, _owner.DeviceName, | ||||
|     //                                    _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage)); | ||||
|     //                        } | ||||
|     //                        else | ||||
|     //                        { | ||||
|     //                            if (!_cancellationToken.IsCancellationRequested && _owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||
|     //                                _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}", | ||||
|     //                                    _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage)); | ||||
|     //                        } | ||||
|  | ||||
|     //                        _variableSourceRead.LastErrorMessage = _readResult.ErrorMessage; | ||||
|     //                        _owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null, _readResult.ErrorMessage); | ||||
|     //                        var time = DateTime.Now; | ||||
|     //                        foreach (var item in _variableSourceRead.VariableRuntimes) | ||||
|     //                        { | ||||
|     //                            item.SetValue(null, time, isOnline: false); | ||||
|     //                        } | ||||
|     //                    } | ||||
|     //                    break; | ||||
|     //            } | ||||
|  | ||||
|     //            return default; | ||||
|     //        } | ||||
|  | ||||
|     //        private async ValueTask AwaitReaderLock() | ||||
|     //        { | ||||
|     //            await _readerLockTask.ConfigureAwait(false); | ||||
|     //            _step = 1; | ||||
|     //            await MoveNextAsync().ConfigureAwait(false); | ||||
|     //        } | ||||
|  | ||||
|     //        private async ValueTask AwaitRead() | ||||
|     //        { | ||||
|     //            await _readTask.ConfigureAwait(false); | ||||
|     //            _step = 3; | ||||
|     //            await MoveNextAsync().ConfigureAwait(false); | ||||
|     //        } | ||||
|  | ||||
|     //        private async ValueTask AwaitReadRetry() | ||||
|     //        { | ||||
|     //            await _readTask.ConfigureAwait(false); | ||||
|     //            _step = 5; | ||||
|     //            await MoveNextAsync().ConfigureAwait(false); | ||||
|     //        } | ||||
|     //    } | ||||
|  | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -172,7 +172,7 @@ public abstract class CollectFoundationBase : CollectBase | ||||
|                 // 从协议读取数据 | ||||
|                 OperResult<ReadOnlyMemory<byte>> read = default; | ||||
|                 var readTask = @this.FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken); | ||||
|                 if (!readTask.IsCompleted) | ||||
|                 if (!readTask.IsCompletedSuccessfully) | ||||
|                 { | ||||
|                     read = await readTask.ConfigureAwait(false); | ||||
|                 } | ||||
| @@ -200,116 +200,6 @@ public abstract class CollectFoundationBase : CollectBase | ||||
|  | ||||
|     } | ||||
|  | ||||
|     ///// <summary> | ||||
|     ///// 采集驱动读取,读取成功后直接赋值变量,失败不做处理,注意非通用设备需重写 | ||||
|     ///// </summary> | ||||
|     //    protected override ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken) | ||||
|     //    { | ||||
|     //        if (cancellationToken.IsCancellationRequested) | ||||
|     //            return new ValueTask<OperResult<ReadOnlyMemory<byte>>>( new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException())); | ||||
|  | ||||
|     //        // 值类型状态机 | ||||
|     //        var stateMachine = new ReadSourceStateMachine(this, variableSourceRead, cancellationToken); | ||||
|     //        return stateMachine.MoveNextAsync(); | ||||
|     //    } | ||||
|  | ||||
|     //    private struct ReadSourceStateMachine | ||||
|     //    { | ||||
|     //        private readonly VariableSourceRead _variableSourceRead; | ||||
|     //        private readonly CancellationToken _cancellationToken; | ||||
|     //        private readonly CollectFoundationBase _owner; | ||||
|     //        private OperResult<ReadOnlyMemory<byte>> _result; | ||||
|     //        private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask; | ||||
|  | ||||
|     //        public ReadSourceStateMachine(CollectFoundationBase owner, VariableSourceRead variableSourceRead, CancellationToken cancellationToken) | ||||
|     //        { | ||||
|     //            _owner = owner; | ||||
|     //            _variableSourceRead = variableSourceRead; | ||||
|     //            _cancellationToken = cancellationToken; | ||||
|     //            _result = default; | ||||
|     //            State = 0; | ||||
|     //        } | ||||
|  | ||||
|     //        public int State { get; private set; } | ||||
|  | ||||
|     //        public ValueTask<OperResult<ReadOnlyMemory<byte>>> MoveNextAsync() | ||||
|     //        { | ||||
|     //            try | ||||
|     //            { | ||||
|     //                switch (State) | ||||
|     //                { | ||||
|     //                    case 0: | ||||
|     //                        // 异步读取 | ||||
|     //                        if (_cancellationToken.IsCancellationRequested) | ||||
|     //                        { | ||||
|     //                            _result = new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException()); | ||||
|     //                            return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result); | ||||
|     //                        } | ||||
|  | ||||
|     //#pragma warning disable CA2012 // 正确使用 ValueTask | ||||
|     //                        _readTask = _owner.FoundationDevice.ReadAsync(_variableSourceRead.AddressObject, _cancellationToken); | ||||
|     //#pragma warning restore CA2012 // 正确使用 ValueTask | ||||
|  | ||||
|     //                        // 检查是否任务已完成 | ||||
|     //                        if (_readTask.IsCompleted) | ||||
|     //                        { | ||||
|     //                            _result = _readTask.Result; | ||||
|     //                            State = 1; | ||||
|     //                            return MoveNextAsync(); | ||||
|     //                        } | ||||
|  | ||||
|     //                        // 如果任务尚未完成,继续等待 | ||||
|     //                        State = 2; | ||||
|     //                        return Awaited(_readTask); | ||||
|  | ||||
|     //                    case 1: | ||||
|     //                        // 解析结构化内容 | ||||
|     //                        if (_result.IsSuccess) | ||||
|     //                        { | ||||
|     //                            var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false); | ||||
|     //                            return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult)); | ||||
|     //                        } | ||||
|  | ||||
|     //                        return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result); | ||||
|  | ||||
|     //                    case 2: | ||||
|     //                        // 完成任务后,解析内容 | ||||
|     //                        _result = _readTask.Result; | ||||
|  | ||||
|     //                        if (_result.IsSuccess) | ||||
|     //                        { | ||||
|     //                            var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false); | ||||
|     //                            return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult)); | ||||
|     //                        } | ||||
|  | ||||
|     //                        return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result); | ||||
|  | ||||
|     //                    default: | ||||
|     //                        throw new InvalidOperationException("Unexpected state."); | ||||
|     //                } | ||||
|     //            } | ||||
|     //            catch (Exception ex) | ||||
|     //            { | ||||
|     //                return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(ex)); | ||||
|     //            } | ||||
|     //        } | ||||
|  | ||||
|     //        private async ValueTask<OperResult<ReadOnlyMemory<byte>>> Awaited(ValueTask<OperResult<ReadOnlyMemory<byte>>> vt) | ||||
|     //        { | ||||
|     //            try | ||||
|     //            { | ||||
|  | ||||
|  | ||||
|     //                await vt.ConfigureAwait(false); | ||||
|     //                return await MoveNextAsync().ConfigureAwait(false); | ||||
|     //            } | ||||
|     //            catch (Exception ex) | ||||
|     //            { | ||||
|     //                return new OperResult<ReadOnlyMemory<byte>>(ex); | ||||
|     //            } | ||||
|     //        } | ||||
|     //    } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 批量写入变量值,需返回变量名称/结果,注意非通用设备需重写 | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -327,8 +327,11 @@ public static class GlobalData | ||||
|         { | ||||
|             if (deviceRuntime.RedundantEnable && deviceRuntime.RedundantDeviceId != null) | ||||
|                 return true; | ||||
|             else if (GlobalData.IdDevices.Any(a => a.Value.RedundantDeviceId == deviceRuntime.Id)) | ||||
|  | ||||
|             var id = deviceRuntime.Id; | ||||
|             foreach (var kv in GlobalData.IdDevices) | ||||
|             { | ||||
|                 if (kv.Value.RedundantDeviceId == id) | ||||
|                     return true; | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -8,7 +8,9 @@ | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all"> | ||||
| 		  <IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| 		</PackageReference> | ||||
| 		<PackageReference Include="Rougamo.Fody" Version="5.0.2" /> | ||||
| 		<PackageReference Include="TouchSocket.Dmtp" Version="$(TSVersion)" /> | ||||
| 		<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="$(TSVersion)" />--> | ||||
|   | ||||
| @@ -1347,27 +1347,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e => | ||||
|     private TreeViewItem<ChannelDeviceTreeItem> UnknownTreeViewItem; | ||||
|  | ||||
|     SmartTriggerScheduler? scheduler; | ||||
|     private bool _initialized; | ||||
|     public override async Task SetParametersAsync(ParameterView parameters) | ||||
|     { | ||||
|         parameters.SetParameterProperties(this); | ||||
|         if (!_initialized) | ||||
|         { | ||||
|             _initialized = true; | ||||
|  | ||||
|             OnInitialized(); | ||||
|             await OnInitializedAsync(); | ||||
|             OnParametersSet(); | ||||
|             StateHasChanged(); | ||||
|             await OnParametersSetAsync(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             OnParametersSet(); | ||||
|             StateHasChanged(); | ||||
|             await OnParametersSetAsync(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     protected override async Task OnInitializedAsync() | ||||
|     { | ||||
| @@ -1610,8 +1590,9 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e => | ||||
|     { | ||||
|  | ||||
|         Disposed = true; | ||||
|         ChannelRuntimeDispatchService.UnSubscribe(Refresh); | ||||
|         ChannelRuntimeDispatchService?.UnSubscribe(Refresh); | ||||
|  | ||||
|         if (Module != null) | ||||
|             await Module.InvokeVoidAsync("dispose", Id); | ||||
|  | ||||
|         await base.DisposeAsync(disposing); | ||||
|   | ||||
| @@ -296,8 +296,9 @@ public partial class VariableRuntimeInfo | ||||
|     { | ||||
|  | ||||
|         Disposed = true; | ||||
|         VariableRuntimeDispatchService.UnSubscribe(Refresh); | ||||
|         VariableRuntimeDispatchService?.UnSubscribe(Refresh); | ||||
|  | ||||
|         if (Module != null) | ||||
|             await Module.InvokeVoidAsync("dispose", Id); | ||||
|  | ||||
|         await base.DisposeAsync(disposing); | ||||
|   | ||||
| @@ -10,7 +10,9 @@ | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\ThingsGateway.Blazor.Diagrams\ThingsGateway.Blazor.Diagrams.csproj" /> | ||||
| 		<ProjectReference Include="..\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all"> | ||||
| 		  <IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| 		</PackageReference> | ||||
| 		<ProjectReference Include="..\..\Admin\ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj" /> | ||||
| 		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj" /> | ||||
| 	</ItemGroup> | ||||
|   | ||||
| @@ -8,14 +8,12 @@ | ||||
| 		<ApplicationIcon>favicon.ico</ApplicationIcon> | ||||
| 		<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> | ||||
| 		 | ||||
| 		 | ||||
| 		<CETCompat>false</CETCompat> | ||||
| 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>true</GarbageCollectionAdaptationMode> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
|  | ||||
| 		<!--使用自托管线程池--> | ||||
| 		<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> --> | ||||
|  | ||||
| 		<!--使用工作站GC--> | ||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -11,11 +11,6 @@ | ||||
| using BenchmarkDotNet.Attributes; | ||||
| using BenchmarkDotNet.Diagnosers; | ||||
|  | ||||
| using Longbow.Modbus; | ||||
| using Longbow.TcpSocket; | ||||
|  | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
|  | ||||
| using System.Net.Sockets; | ||||
|  | ||||
| using ThingsGateway.Foundation.Modbus; | ||||
| @@ -33,14 +28,12 @@ namespace ThingsGateway.Foundation; | ||||
| [MemoryDiagnoser] | ||||
| public class ModbusBenchmark : IDisposable | ||||
| { | ||||
|     public static int ClientCount = 1; | ||||
|     public static int ClientCount = 10; | ||||
|     public static int TaskNumberOfItems = 1; | ||||
|     public static int NumberOfItems = 10; | ||||
|     public static int NumberOfItems = 100; | ||||
|  | ||||
|     private readonly List<IModbusClient> _lgbModbusClients = []; | ||||
|     private List<ModbusMaster> thingsgatewaymodbuss = new(); | ||||
|     private List<IModbusMaster> nmodbuss = new(); | ||||
|     //private List<ModbusTcpNet> modbusTcpNets = new(); | ||||
|     private List<ModbusTcpMaster> modbusTcpMasters = new(); | ||||
|  | ||||
|     [GlobalSetup] | ||||
| @@ -74,15 +67,7 @@ public class ModbusBenchmark : IDisposable | ||||
|             await nmodbus.ReadHoldingRegistersAsync(1, 0, 100); | ||||
|             nmodbuss.Add(nmodbus); | ||||
|         } | ||||
|         //for (int i = 0; i < ClientCount; i++) | ||||
|         //{ | ||||
|         //    ModbusTcpNet modbusTcpNet = new(); | ||||
|         //    modbusTcpNet.IpAddress = "127.0.0.1"; | ||||
|         //    modbusTcpNet.Port = 502; | ||||
|         //    modbusTcpNet.ConnectServer(); | ||||
|         //    modbusTcpNet.ReadAsync("0", 100); | ||||
|         //    modbusTcpNets.Add(modbusTcpNet); | ||||
|         //} | ||||
|  | ||||
|  | ||||
|         for (int i = 0; i < ClientCount; i++) | ||||
|         { | ||||
| @@ -94,23 +79,6 @@ public class ModbusBenchmark : IDisposable | ||||
|             modbusTcpMasters.Add(client); | ||||
|         } | ||||
|  | ||||
|         { | ||||
|             var sc = new ServiceCollection(); | ||||
|             sc.AddTcpSocketFactory(); | ||||
|             sc.AddModbusFactory(); | ||||
|  | ||||
|             var provider = sc.BuildServiceProvider(); | ||||
|             var factory = provider.GetRequiredService<IModbusFactory>(); | ||||
|  | ||||
|             for (int i = 0; i < ClientCount; i++) | ||||
|             { | ||||
|                 var client = factory.GetOrCreateTcpMaster(); | ||||
|                 await client.ConnectAsync("127.0.0.1", 502); | ||||
|                 await client.ReadHoldingRegistersAsync(0x01, 0x00, 10); | ||||
|  | ||||
|                 _lgbModbusClients.Add(client); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [Benchmark] | ||||
| @@ -140,33 +108,6 @@ public class ModbusBenchmark : IDisposable | ||||
|         await Task.WhenAll(tasks); | ||||
|     } | ||||
|  | ||||
|     [Benchmark] | ||||
|     public async Task LongbowModbus() | ||||
|     { | ||||
|         List<Task> tasks = new List<Task>(); | ||||
|         foreach (var client in _lgbModbusClients) | ||||
|         { | ||||
|  | ||||
|             for (int i = 0; i < TaskNumberOfItems; i++) | ||||
|             { | ||||
|                 tasks.Add(Task.Run(async () => | ||||
|                 { | ||||
|                     for (int i = 0; i < NumberOfItems; i++) | ||||
|                     { | ||||
|                         using var cts = new CancellationTokenSource(3000); | ||||
|                         var result = await client.ReadHoldingRegistersAsync(1, 0, 100, cts.Token).ConfigureAwait(false); | ||||
|                         var data = result.ReadUShortValues(100); | ||||
|                         if (!result.IsSuccess) | ||||
|                         { | ||||
|                             throw new Exception(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + result.Exception); | ||||
|                         } | ||||
|                     } | ||||
|                 })); | ||||
|             } | ||||
|         } | ||||
|         await Task.WhenAll(tasks); | ||||
|     } | ||||
|  | ||||
|     [Benchmark] | ||||
|     public async Task TouchSocket() | ||||
|     { | ||||
| @@ -214,39 +155,12 @@ public class ModbusBenchmark : IDisposable | ||||
|     } | ||||
|  | ||||
|  | ||||
|     //并发失败 | ||||
|     //[Benchmark] | ||||
|     //public async Task HslCommunication() | ||||
|     //{ | ||||
|     //    List<Task> tasks = new List<Task>(); | ||||
|     //    foreach (var modbusTcpNet in modbusTcpNets) | ||||
|     //    { | ||||
|     //        for (int i = 0; i < TaskNumberOfItems; i++) | ||||
|     //        { | ||||
|     //            tasks.Add(Task.Run(async () => | ||||
|     //            { | ||||
|     //                for (int i = 0; i < NumberOfItems; i++) | ||||
|     //                { | ||||
|     //                    var result = await modbusTcpNet.ReadAsync("0", 100); | ||||
|     //                    if (!result.IsSuccess) | ||||
|     //                    { | ||||
|     //                        throw new Exception(result.Message); | ||||
|     //                    } | ||||
|     //                } | ||||
|     //            })); | ||||
|     //        } | ||||
|     //    } | ||||
|     //    await Task.WhenAll(tasks); | ||||
|     //} | ||||
|  | ||||
|     public void Dispose() | ||||
|     { | ||||
|  | ||||
|         thingsgatewaymodbuss?.ForEach(a => a.Channel.SafeDispose()); | ||||
|         thingsgatewaymodbuss?.ForEach(a => a.SafeDispose()); | ||||
|         nmodbuss?.ForEach(a => a.SafeDispose()); | ||||
|         //modbusTcpNets?.ForEach(a => a.SafeDispose()); | ||||
|         _lgbModbusClients?.ForEach(a => a.DisposeAsync()); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -11,8 +11,6 @@ | ||||
| using BenchmarkDotNet.Attributes; | ||||
| using BenchmarkDotNet.Diagnosers; | ||||
|  | ||||
| using HslCommunication.Profinet.Siemens; | ||||
|  | ||||
| using S7.Net; | ||||
|  | ||||
| using ThingsGateway.Foundation.SiemensS7; | ||||
| @@ -33,7 +31,6 @@ public class S7Benchmark : IDisposable | ||||
|     private List<SiemensS7Master> siemensS7s = new(); | ||||
|  | ||||
|     private List<Plc> plcs = new(); | ||||
|     private List<SiemensS7Net> siemensS7Nets = new(); | ||||
|  | ||||
|     [GlobalSetup] | ||||
|     public async Task Init() | ||||
| @@ -57,13 +54,7 @@ public class S7Benchmark : IDisposable | ||||
|                 await siemensS7.ReadAsync("M1", 100); | ||||
|                 siemensS7s.Add(siemensS7); | ||||
|             } | ||||
|             for (int i = 0; i < ClientCount; i++) | ||||
|             { | ||||
|                 var siemensS7Net = new SiemensS7Net(SiemensPLCS.S1500, "127.0.0.1"); | ||||
|                 await siemensS7Net.ConnectServerAsync(); | ||||
|                 await siemensS7Net.ReadAsync("M0", 100); | ||||
|                 siemensS7Nets.Add(siemensS7Net); | ||||
|             } | ||||
|  | ||||
|             for (int i = 0; i < ClientCount; i++) | ||||
|             { | ||||
|                 var plc = new Plc(CpuType.S71500, "127.0.0.1", 102, 0, 0); | ||||
| @@ -94,34 +85,6 @@ public class S7Benchmark : IDisposable | ||||
|         await Task.WhenAll(tasks); | ||||
|     } | ||||
|  | ||||
|     [Benchmark] | ||||
|     public async Task HslCommunication() | ||||
|     { | ||||
|         List<Task> tasks = new List<Task>(); | ||||
|         foreach (var siemensS7Net in siemensS7Nets) | ||||
|         { | ||||
|             for (int i = 0; i < TaskNumberOfItems; i++) | ||||
|             { | ||||
|                 tasks.Add(Task.Run(async () => | ||||
|                 { | ||||
|                     for (int i = 0; i < NumberOfItems; i++) | ||||
|                     { | ||||
|                         var result = await siemensS7Net.ReadAsync("M0", 100); | ||||
|                         if (!result.IsSuccess) | ||||
|                         { | ||||
|                             throw new Exception(result.Message); | ||||
|                         } | ||||
|                     } | ||||
|                 })); | ||||
|             } | ||||
|         } | ||||
|         await Task.WhenAll(tasks); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     [Benchmark] | ||||
|     public async Task ThingsGateway() | ||||
| @@ -151,7 +114,6 @@ public class S7Benchmark : IDisposable | ||||
|     public void Dispose() | ||||
|     { | ||||
|         plcs.ForEach(a => a.SafeDispose()); | ||||
|         siemensS7Nets.ForEach(a => a.SafeDispose()); | ||||
|         siemensS7s.ForEach(a => a.Channel.SafeDispose()); | ||||
|         siemensS7s.ForEach(a => a.SafeDispose()); | ||||
|     } | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
| using BenchmarkDotNet.Configs; | ||||
| using BenchmarkDotNet.Running; | ||||
|  | ||||
| namespace BenchmarkConsoleApp | ||||
| namespace ThingsGateway.Foundation | ||||
| { | ||||
|     internal class Program | ||||
|     { | ||||
| @@ -45,18 +45,18 @@ namespace BenchmarkConsoleApp | ||||
|             //ManualConfig.Create(DefaultConfig.Instance) | ||||
|             //.WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||
|             //); | ||||
|             BenchmarkRunner.Run<BenchmarkAsyncWaitData>( | ||||
| ManualConfig.Create(DefaultConfig.Instance) | ||||
| .WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||
| ); | ||||
|             //            BenchmarkRunner.Run<BenchmarkAsyncWaitData>( | ||||
|             //ManualConfig.Create(DefaultConfig.Instance) | ||||
|             //.WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||
|             //); | ||||
|             //         BenchmarkRunner.Run<SemaphoreBenchmark>( | ||||
|             //    ManualConfig.Create(DefaultConfig.Instance) | ||||
|             //        .WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||
|             //); | ||||
|             //            BenchmarkRunner.Run<ModbusBenchmark>( | ||||
|             //ManualConfig.Create(DefaultConfig.Instance) | ||||
|             //.WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||
|             //); | ||||
|             BenchmarkRunner.Run<ModbusBenchmark>( | ||||
| ManualConfig.Create(DefaultConfig.Instance) | ||||
| .WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||
| ); | ||||
|             //            BenchmarkRunner.Run<S7Benchmark>( | ||||
|             //ManualConfig.Create(DefaultConfig.Instance) | ||||
|             //.WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||
|   | ||||
| @@ -42,10 +42,7 @@ | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="BenchmarkDotNet" Version="0.15.4" /> | ||||
| 		<PackageReference Include="HslCommunication" Version="12.5.1" /> | ||||
| 		<PackageReference Include="Longbow.Modbus" Version="9.1.1" /> | ||||
| 		<PackageReference Include="NModbus" Version="3.0.81" /> | ||||
| 		<PackageReference Include="NModbus.Serial" Version="3.0.81" /> | ||||
| 		<PackageReference Include="S7netplus" Version="0.20.0" /> | ||||
| 		<!--<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="$(DefaultVersion)" /> | ||||
| 		<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="$(DefaultVersion)" />--> | ||||
|   | ||||
| @@ -1,14 +1,4 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://thingsgateway.cn/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Modbus; | ||||
| namespace ThingsGateway.Foundation.Modbus; | ||||
|  | ||||
| /// <summary> | ||||
| /// <inheritdoc/> | ||||
|   | ||||
| @@ -558,7 +558,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static bool TryParseRequest(object requestInfo, out ModbusRequest modbusRequest, out ReadOnlySequence<byte> sequences, out bool modbusRtu) | ||||
|     private static bool TryParseRequest(IRequestInfo requestInfo, out ModbusRequest modbusRequest, out ReadOnlySequence<byte> sequences, out bool modbusRtu) | ||||
|     { | ||||
|         modbusRequest = default; | ||||
|         sequences = default; | ||||
| @@ -599,25 +599,29 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|             return WriteError(modbusRtu, client, sequences, e); | ||||
|         } | ||||
|  | ||||
|         return Write(this, client, e, modbusRequest, sequences, modbusRtu, data); | ||||
|  | ||||
|         static async PooledTask Write(ModbusSlave @this, IClientChannel client, ReceivedDataEventArgs e, ModbusRequest modbusRequest, ReadOnlySequence<byte> sequences, bool modbusRtu, OperResult<ReadOnlyMemory<byte>> data) | ||||
|         { | ||||
|         ValueByteBlock byteBlock = new(1024); | ||||
|         try | ||||
|         { | ||||
|             WriteReadResponse(modbusRequest, sequences, data.Content, ref byteBlock, modbusRtu); | ||||
|                 await @this.ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); | ||||
|             return SendAndDisposeAsync(this, client, e, modbusRtu, byteBlock); | ||||
|         } | ||||
|         catch | ||||
|         { | ||||
|                 await @this.WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false); | ||||
|             byteBlock.SafeDispose(); | ||||
|             return WriteError(modbusRtu, client, sequences, e); | ||||
|         } | ||||
|         static async PooledTask SendAndDisposeAsync(ModbusSlave @this, IClientChannel client, ReceivedDataEventArgs e, bool modbusRtu, ValueByteBlock byteBlock) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await @this.ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 byteBlock.SafeDispose(); | ||||
|                 byteBlock.SafeDispose(); //即使 ValueByteBlock 是 struct,内部仍持有同一块 Memory<byte> 引用,可以安全释放 | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private Task HandleWriteRequestAsync( | ||||
|   | ||||
| @@ -15,7 +15,9 @@ | ||||
| 		<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj"> | ||||
| 		</ProjectReference> | ||||
| 		 | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all"> | ||||
| 		  <IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| 		</PackageReference> | ||||
|  | ||||
| 	</ItemGroup> | ||||
|  | ||||
|   | ||||
| @@ -64,12 +64,10 @@ | ||||
| 		<ApplicationIcon>favicon.ico</ApplicationIcon> | ||||
| 		<!--<PublishAot>true</PublishAot>--> | ||||
| 		<CETCompat>false</CETCompat> | ||||
|  | ||||
| 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
|  | ||||
| 		<!--使用工作站GC--> | ||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> | ||||
| 		<!--<PlatformTarget>x86</PlatformTarget>--> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk.Razor"> | ||||
| <Project Sdk="Microsoft.NET.Sdk.Razor"> | ||||
|  | ||||
| 	<Import Project="..\Version.props" /> | ||||
| 	 | ||||
| @@ -37,17 +37,15 @@ | ||||
| 		<ApplicationIcon>favicon.ico</ApplicationIcon> | ||||
| 		<!--<PublishAot>true</PublishAot>--> | ||||
| 		<CETCompat>false</CETCompat> | ||||
| 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
|  | ||||
| 		<PublishAot>true</PublishAot> | ||||
| 		<DebugType>none</DebugType> | ||||
| 		<EmbedAllSources>false</EmbedAllSources> | ||||
| 		<EmitDebugInformation>false</EmitDebugInformation> | ||||
| 		 | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
| 		 | ||||
| 		<!--使用工作站GC--> | ||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> | ||||
| 		<!--<PlatformTarget>x86</PlatformTarget>--> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|   | ||||
| @@ -32,26 +32,14 @@ | ||||
| 		<PublishReadyToRunComposite>true</PublishReadyToRunComposite> | ||||
| 		<ApplicationIcon>favicon.ico</ApplicationIcon> | ||||
|  | ||||
| 		<CETCompat>false</CETCompat> | ||||
| 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
|  | ||||
|  | ||||
| 		<CETCompat>false</CETCompat> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| 		<!--<TieredCompilation>false</TieredCompilation>--> | ||||
|  | ||||
| 		<!--使用自托管线程池--> | ||||
| 		<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> --> | ||||
|  | ||||
| 		<!--使用工作站GC--> | ||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> | ||||
|  | ||||
| 		<!--<PlatformTarget>x86</PlatformTarget>--> | ||||
| 		<!--editbin /LARGEADDRESSAWARE:NO ThingsGateway.Server.exe--> | ||||
|  | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|   }, | ||||
|  | ||||
|   "RemoteServerManagement": { | ||||
|     "Enable": true, | ||||
|     "Enable": false, | ||||
|     "Name": "ThingsGateway", | ||||
|     "ServerUri": "0.0.0.0:8399", | ||||
|     "VerifyToken": "ThingsGateway", | ||||
|   | ||||
| @@ -111,7 +111,8 @@ public class Startup : AppStartup | ||||
|                  options.RootComponents.MaxJSRootComponents = 500; | ||||
|                  options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); | ||||
|                  options.MaxBufferedUnacknowledgedRenderBatches = 20; | ||||
|                  options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); | ||||
|                  options.DisconnectedCircuitMaxRetained = 1; | ||||
|                  options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10); | ||||
|              }) | ||||
|              .AddHubOptions(options => | ||||
|              { | ||||
| @@ -131,7 +132,8 @@ public class Startup : AppStartup | ||||
|                         options.RootComponents.MaxJSRootComponents = 500; | ||||
|                         options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); | ||||
|                         options.MaxBufferedUnacknowledgedRenderBatches = 20; | ||||
|                         options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); | ||||
|                         options.DisconnectedCircuitMaxRetained = 1; | ||||
|                         options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10); | ||||
|                     }).AddHubOptions(options => | ||||
|                     { | ||||
|                         //单个传入集线器消息的最大大小。默认 32 KB | ||||
|   | ||||
| @@ -51,26 +51,11 @@ | ||||
| 		<PublishReadyToRunComposite>true</PublishReadyToRunComposite> | ||||
| 		<ApplicationIcon>favicon.ico</ApplicationIcon> | ||||
|  | ||||
| 		<CETCompat>false</CETCompat> | ||||
| 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||
| 		<!--动态适用GC--> | ||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||
|  | ||||
|  | ||||
| 		<CETCompat>false</CETCompat> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| 		<!--<TieredCompilation>false</TieredCompilation>--> | ||||
|  | ||||
| 		<!--使用自托管线程池--> | ||||
| 		<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> --> | ||||
|  | ||||
| 		<!--使用工作站GC--> | ||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> | ||||
|  | ||||
| 		<!--<PlatformTarget>x86</PlatformTarget>--> | ||||
| 		<!--editbin /LARGEADDRESSAWARE:NO ThingsGateway.Server.exe--> | ||||
|  | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,9 @@ | ||||
| { | ||||
|   "configProperties": { | ||||
|     "System.Runtime.EnableWriteXorExecute": false | ||||
|     "System.Runtime.EnableWriteXorExecute": false, | ||||
|     "System.GC.HeapHardLimitPercent": 95, //堆限制百分比 | ||||
|     "System.GC.HighMemoryPercent": 90, //高内存百分比 | ||||
|     "System.GC.DynamicAdaptationMode": 1 //动态适应模式 | ||||
|     //"System.GC.RegionRange": 549755813888 //8GB, 区域范围,保留的虚拟内存,如DOCKER内出现OOM,可以调大,一般是进程内存限制的2倍 | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										302
									
								
								wiki.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								wiki.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,302 @@ | ||||
| { | ||||
|   "repo_notes": [ | ||||
|     { | ||||
|       "content": "" | ||||
|     } | ||||
|   ], | ||||
|   "pages": [ | ||||
|     { | ||||
|       "title": "Overview", | ||||
|       "purpose": "Introduce ThingsGateway, explaining what it is, its core purpose as an industrial IoT gateway, and the high-level architecture", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Key Features", | ||||
|       "purpose": "List and describe the main features including multi-protocol support, plugin architecture, data persistence options, and deployment models", | ||||
|       "parent": "Overview", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "System Requirements and Dependencies", | ||||
|       "purpose": "Document the .NET versions, target frameworks, required NuGet packages, and system requirements", | ||||
|       "parent": "Overview", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Getting Started", | ||||
|       "purpose": "Provide quick-start guide for installing and running ThingsGateway", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Installation and Deployment", | ||||
|       "purpose": "Explain deployment options including Docker (x64/ARM64), web server, desktop application, and configuration basics", | ||||
|       "parent": "Getting Started", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Initial Configuration", | ||||
|       "purpose": "Guide users through initial setup including appsettings.json, environment variables, logging configuration, and first channel/device setup", | ||||
|       "parent": "Getting Started", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Architecture Overview", | ||||
|       "purpose": "Explain the overall system architecture including layered design, separation of concerns, and component relationships", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Layered Architecture", | ||||
|       "purpose": "Describe the Foundation, Gateway Application, and UI/Admin layers and their responsibilities", | ||||
|       "parent": "Architecture Overview", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Plugin System Architecture", | ||||
|       "purpose": "Explain the plugin architecture including Foundation vs Plugin layer separation, dynamic loading with AssemblyLoadContext, and plugin types", | ||||
|       "parent": "Architecture Overview", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Build System and Conditional Compilation", | ||||
|       "purpose": "Document the MSBuild-based build system including Directory.Build.props, conditional plugin loading, multi-targeting, and NuGet package generation", | ||||
|       "parent": "Architecture Overview", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Foundation Layer", | ||||
|       "purpose": "Document the core foundation libraries that provide protocol implementations and communication primitives", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Device Communication", | ||||
|       "purpose": "Explain DeviceBase abstraction, IDevice interface, protocol implementations, read/write operations, and data parsing", | ||||
|       "parent": "Foundation Layer", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Channel Architecture", | ||||
|       "purpose": "Document IChannel interface, channel implementations (TCP/UDP/Serial), channel options, and connection lifecycle", | ||||
|       "parent": "Foundation Layer", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Data Handling and Transformation", | ||||
|       "purpose": "Describe DataHandlingAdapter, message parsing, ThingsGatewayBitConverter for endianness/format conversion, and WaitHandlePool for request-response correlation", | ||||
|       "parent": "Foundation Layer", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Core Utilities", | ||||
|       "purpose": "Document TimerX scheduling, ExpiringDictionary caching, WaitLock concurrency primitives, and Reflect utilities", | ||||
|       "parent": "Foundation Layer", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Gateway Application", | ||||
|       "purpose": "Explain the main gateway application layer that orchestrates device communication and data flow", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Runtime Management System", | ||||
|       "purpose": "Document the entity-to-runtime conversion, GlobalData static registry, RuntimeServiceHelper, and runtime synchronization", | ||||
|       "parent": "Gateway Application", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Device Lifecycle Management", | ||||
|       "purpose": "Explain DeviceThreadManage, driver initialization, StartAsync/StopAsync lifecycle, TaskSchedulerLoop, and thread management", | ||||
|       "parent": "Gateway Application", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Variable Management", | ||||
|       "purpose": "Document VariableRuntime, VariableSourceRead, variable packing optimization, and data collection workflow", | ||||
|       "parent": "Gateway Application", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Concurrency and Read/Write Coordination", | ||||
|       "purpose": "Explain AsyncReadWriteLock, reader/writer priority, duty cycle control, and concurrent operation management", | ||||
|       "parent": "Gateway Application", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Event System", | ||||
|       "purpose": "Document GlobalData event dispatchers, VariableValueChangeEvent, DeviceStatusChangeEvent, AlarmChangedEvent, and event flow", | ||||
|       "parent": "Gateway Application", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Redundancy and Failover", | ||||
|       "purpose": "Explain device redundancy system, master/slave configuration, failover triggers, and variable state transfer", | ||||
|       "parent": "Gateway Application", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Plugin Development", | ||||
|       "purpose": "Guide for developing custom plugins for data collection and business logic integration", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Collection Plugins (CollectBase)", | ||||
|       "purpose": "Document how to create data collection plugins including protocol implementation, variable loading, and scheduled tasks", | ||||
|       "parent": "Plugin Development", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Business Plugins (BusinessBase)", | ||||
|       "purpose": "Explain business plugin development for data persistence, distribution, and serving protocols", | ||||
|       "parent": "Plugin Development", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Plugin Service and Dynamic Loading", | ||||
|       "purpose": "Document PluginService, AssemblyLoadContext usage, plugin discovery, driver instantiation, and property configuration", | ||||
|       "parent": "Plugin Development", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Plugin Packaging and Distribution", | ||||
|       "purpose": "Explain how to package plugins as NuGet packages, versioning, and deployment via Directory.build.targets", | ||||
|       "parent": "Plugin Development", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Built-in Protocols", | ||||
|       "purpose": "Document the built-in protocol implementations including Modbus, Siemens S7, OPC UA, OPC DA, and Dlt645", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Modbus Protocol", | ||||
|       "purpose": "Document Modbus Master/Slave implementations, RTU/TCP/UDP support, function codes, and address parsing", | ||||
|       "parent": "Built-in Protocols", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "title": "Siemens S7 Protocol", | ||||
|       "purpose": "Explain S7 Master implementation, PLC types, area addressing, and data block access", | ||||
|       "parent": "Built-in Protocols", | ||||
|       "page_notes": [ | ||||
|         { | ||||
|           "content": "" | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   ] | ||||
| } | ||||
		Reference in New Issue
	
	Block a user