mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 07:33:58 +08:00 
			
		
		
		
	Compare commits
	
		
			39 Commits
		
	
	
		
			10.11.96.0
			...
			10.12.8.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0c31cfcbc5 | ||
|   | 0e44bc67cd | ||
|   | 12dfbba42c | ||
|   | 96b4287f3a | ||
|   | 7d406de29f | ||
|   | 81f0ef466a | ||
|   | 3f2d6b133c | ||
|   | e776dc67eb | ||
| ![devin-ai-integration[bot]](/assets/img/avatar_default.png)  | bc5827d140 | ||
|   | 21838bf4af | ||
|   | 6090108597 | ||
|   | b47b9e6f43 | ||
|   | 18d1cffb2d | ||
|   | 516fd7f235 | ||
|   | 2ee16c3533 | ||
|   | 7d22f5c78e | ||
|   | 3e604ee2fd | ||
|   | 47e442874c | ||
|   | 2a8c0cbab1 | ||
|   | c26898b49d | ||
|   | 00c24d06a3 | ||
|   | 3461f34240 | ||
|   | aa1ce08c02 | ||
|   | 9c230c2da9 | ||
|   | 21215d0379 | ||
|   | 7448183791 | ||
|   | 35edd7dc43 | ||
|   | bd178831e3 | ||
|   | fe9ec6ad10 | ||
|   | 6f9ec2e24b | ||
|   | c0337e2b19 | ||
|   | 8a95f48f5a | ||
|   | 14f3c31265 | ||
|   | 1bad65378f | ||
|   | db3affc67e | ||
|   | 5ee8b50a92 | ||
|   | 301beda2a2 | ||
|   | 628b51a353 | ||
|   | f03445bc83 | 
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,11 +1,18 @@ | ||||
| # ThingsGateway | ||||
|  | ||||
|  | ||||
| [](https://gitee.com/ThingsGateway/ThingsGateway/stargazers)  | ||||
| [](https://github.com/ThingsGateway/ThingsGateway) | ||||
| [](https://deepwiki.com/ThingsGateway/ThingsGateway) | ||||
| [](https://www.nuget.org/packages/ThingsGateway.Foundation/) | ||||
| [](https://www.nuget.org/packages/ThingsGateway.Foundation/) | ||||
| [](https://thingsgateway.cn/docs/1) | ||||
| <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569"> | ||||
| <img src="https://img.shields.io/badge/QQ群-605534569-red" alt="QQ"> | ||||
| </a> | ||||
|  | ||||
| ## Introduction | ||||
|  | ||||
|  | ||||
| A cross-platform, high-performance edge data collection gateway based on net9. | ||||
| A cross-platform, high-performance edge data collection gateway based on net8/10. | ||||
|  | ||||
|  | ||||
| ## Documentation | ||||
| @@ -29,7 +36,6 @@ Account: **SuperAdmin** | ||||
| Password: **111111** | ||||
|  | ||||
|  | ||||
| **In the upper-right corner, switch to the IoT Gateway module in the personal popup box** | ||||
|  | ||||
| ## Docker | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,18 @@ | ||||
| # ThingsGateway | ||||
|  | ||||
| [](https://gitee.com/ThingsGateway/ThingsGateway/stargazers)  | ||||
| [](https://github.com/ThingsGateway/ThingsGateway) | ||||
| [](https://deepwiki.com/ThingsGateway/ThingsGateway) | ||||
| [](https://www.nuget.org/packages/ThingsGateway.Foundation/) | ||||
| [](https://www.nuget.org/packages/ThingsGateway.Foundation/) | ||||
| [](https://thingsgateway.cn/docs/1) | ||||
| <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569"> | ||||
| <img src="https://img.shields.io/badge/QQ群-605534569-red" alt="QQ"> | ||||
| </a> | ||||
|  | ||||
| ## 介绍 | ||||
|  | ||||
| 基于net9的跨平台高性能边缘采集网关 | ||||
| 基于net8/10的跨平台高性能边缘采集网关 | ||||
|  | ||||
| ## 文档 | ||||
|  | ||||
| @@ -19,7 +29,6 @@ | ||||
|  | ||||
| 密码 : **111111** | ||||
|  | ||||
| **右上角个人弹出框中,切换到物联网关模块** | ||||
|  | ||||
| ## Docker | ||||
|  | ||||
|   | ||||
| @@ -251,11 +251,13 @@ public class RequestAuditFilter : IAsyncActionFilter, IOrderedFilter | ||||
|  | ||||
|         if (exception == null) | ||||
|         { | ||||
|             logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}"); | ||||
|             if (logger.IsEnabled(LogLevel.Information)) | ||||
|                 logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}"); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}"); | ||||
|             if (logger.IsEnabled(LogLevel.Warning)) | ||||
|                 logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,9 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.ComponentModel; | ||||
| using System.Runtime; | ||||
|  | ||||
| using ThingsGateway.NewLife; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
| @@ -19,11 +22,7 @@ public class HardwareInfo | ||||
|     /// 当前磁盘信息 | ||||
|     /// </summary> | ||||
|     public DriveInfo DriveInfo { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 硬件信息获取 | ||||
|     /// </summary> | ||||
|     public MachineInfo? MachineInfo { get; set; } | ||||
|   | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 主机环境 | ||||
| @@ -40,19 +39,118 @@ public class HardwareInfo | ||||
|     /// </summary> | ||||
|     public string OsArchitecture { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 唯一编码 | ||||
|     /// </summary> | ||||
|     public string UUID { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 进程占用内存 | ||||
|     /// </summary> | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public int WorkingSet { get; set; } | ||||
|     /// <summary>系统名称</summary> | ||||
|     public String OSName { get; set; } | ||||
|  | ||||
|     /// <summary>系统版本</summary> | ||||
|     public String OSVersion { get; set; } | ||||
|     public String UUID { get; set; } | ||||
|  | ||||
|     /// <summary>内存总量。单位MB</summary> | ||||
|     public UInt64 Memory { get; set; } | ||||
|  | ||||
|     /// <summary>可用内存。单位MB</summary> | ||||
|     public UInt64 AvailableMemory { get; set; } | ||||
|  | ||||
|     /// <summary>CPU占用率</summary> | ||||
|     public Double CpuRate { get; set; } | ||||
|     public Double Battery { get; set; } | ||||
|     public Double Temperature { get; set; } | ||||
|  | ||||
|  | ||||
|     /// <summary>处理器型号</summary> | ||||
|     public String? Processor { get; set; } | ||||
|     #region GC与进程内存信息 | ||||
|  | ||||
|     /// <summary>GC 认为“内存吃紧”的阈值。单位:MB</summary> | ||||
|     [DisplayName("GC高内存阈值")] | ||||
|     public UInt64 HighMemoryLoadThreshold { get; set; } | ||||
|  | ||||
|     /// <summary>GC 可用内存上限。单位:MB</summary> | ||||
|     [DisplayName("GC可用内存上限")] | ||||
|     public UInt64 TotalAvailableMemory { get; set; } | ||||
|  | ||||
|     /// <summary>当前托管堆容量。单位:MB</summary> | ||||
|     [DisplayName("托管堆容量")] | ||||
|     public UInt64 HeapSize { get; set; } | ||||
|  | ||||
|     /// <summary>托管堆已用内存。单位:MB</summary> | ||||
|     [DisplayName("托管堆已用")] | ||||
|     public UInt64 TotalMemory { get; set; } | ||||
|  | ||||
|     /// <summary>托管堆碎片大小。单位:MB</summary> | ||||
|     [DisplayName("托管堆碎片")] | ||||
|     public UInt64 FragmentedBytes { get; set; } | ||||
|  | ||||
|     /// <summary>GC识别可用内存。单位:MB</summary> | ||||
|     [DisplayName("GC识别可用内存")] | ||||
|     public UInt64 GCAvailableMemory { get; set; } | ||||
|  | ||||
|     /// <summary>GC 已提交的内存。单位:MB</summary> | ||||
|     [DisplayName("GC已提交内存")] | ||||
|     public UInt64 CommittedBytes { get; set; } | ||||
|  | ||||
|     /// <summary>GC 累计分配的托管内存。单位:MB</summary> | ||||
|     [DisplayName("GC累计分配")] | ||||
|     public UInt64 TotalAllocatedBytes { get; set; } | ||||
|     /// <summary>GC 暂停累计时间。单位:毫秒</summary> | ||||
|     [DisplayName("GC累计暂停时间")] | ||||
|     public UInt64 TotalPauseDurationMs { get; set; } | ||||
|     /// <summary>GC 代0收集次数</summary> | ||||
|     [DisplayName("GC Gen0 次数")] | ||||
|     public Int32 GcGen0Count { get; set; } | ||||
|  | ||||
|     /// <summary>GC 代1收集次数</summary> | ||||
|     [DisplayName("GC Gen1 次数")] | ||||
|     public Int32 GcGen1Count { get; set; } | ||||
|  | ||||
|     /// <summary>GC 代2收集次数</summary> | ||||
|     [DisplayName("GC Gen2 次数")] | ||||
|     public Int32 GcGen2Count { get; set; } | ||||
|  | ||||
|     /// <summary>Server GC 是否启用</summary> | ||||
|     [DisplayName("是否使用Server GC")] | ||||
|     public Boolean IsServerGC { get; set; } | ||||
|  | ||||
|     /// <summary>GC 延迟模式</summary> | ||||
|     [DisplayName("GC延迟模式")] | ||||
|     public GCLatencyMode? GCLatencyMode { get; set; } | ||||
|  | ||||
|     /// <summary>GC 固定对象数</summary> | ||||
|     [DisplayName("固定对象数")] | ||||
|     public Int64 PinnedObjectsCount { get; set; } | ||||
|  | ||||
|     /// <summary>终结队列挂起对象数</summary> | ||||
|     [DisplayName("终结挂起数")] | ||||
|     public Int64 FinalizationPendingCount { get; set; } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region 进程内存信息 | ||||
|  | ||||
|     /// <summary>进程虚拟内存使用量。单位:MB</summary> | ||||
|     [DisplayName("虚拟内存")] | ||||
|     public UInt64 VirtualMemory { get; set; } | ||||
|  | ||||
|     /// <summary>进程私有内存使用量。单位:MB</summary> | ||||
|     [DisplayName("私有内存")] | ||||
|     public UInt64 PrivateMemory { get; set; } | ||||
|  | ||||
|     /// <summary>进程峰值工作集。单位:MB</summary> | ||||
|     [DisplayName("峰值工作集")] | ||||
|     public UInt64 PeakWorkingSet { get; set; } | ||||
|  | ||||
|     /// <summary>进程当前工作集。单位:MB</summary> | ||||
|     [DisplayName("当前工作集")] | ||||
|     public UInt64 WorkingSet { get; set; } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 更新时间 | ||||
|     /// </summary> | ||||
|     public string UpdateTime { get; set; } | ||||
|     public DateTime UpdateTime { get; set; } | ||||
|     public ulong AppRunTotalMinute { get;  set; } | ||||
|     public ulong SystemRunTotalMinute { get;  set; } | ||||
| } | ||||
|   | ||||
| @@ -8,12 +8,10 @@ | ||||
| // QQ群:605534569 | ||||
| // ------------------------------------------------------------------------------ | ||||
|  | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using System.Runtime.InteropServices; | ||||
|  | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Caching; | ||||
| using ThingsGateway.NewLife.Threading; | ||||
| @@ -43,7 +41,7 @@ public class HardwareJob : IJob, IHardwareJob | ||||
|     /// <summary> | ||||
|     /// 运行信息获取 | ||||
|     /// </summary> | ||||
|     public HardwareInfo HardwareInfo { get; } = new(); | ||||
|     public HardwareInfo HardwareInfo { get; private set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public HardwareInfoOptions HardwareInfoOptions { get; private set; } | ||||
| @@ -76,9 +74,10 @@ public class HardwareJob : IJob, IHardwareJob | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 if (HardwareInfo.MachineInfo == null) | ||||
|                     var machine = MachineInfo.GetCurrent(); | ||||
|                 if (HardwareInfo == null) | ||||
|                 { | ||||
|                     HardwareInfo.MachineInfo = MachineInfo.GetCurrent(); | ||||
|                     HardwareInfo=machine.AdaptHardwareInfo(); | ||||
|  | ||||
|                     string currentPath = Directory.GetCurrentDirectory(); | ||||
|                     DriveInfo drive = new(Path.GetPathRoot(currentPath)); | ||||
| @@ -88,10 +87,9 @@ public class HardwareJob : IJob, IHardwareJob | ||||
|                     HardwareInfo.DriveInfo = drive; | ||||
|                     HardwareInfo.OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(); // 系统架构 | ||||
|                     HardwareInfo.FrameworkDescription = RuntimeInformation.FrameworkDescription; // NET框架 | ||||
|                     HardwareInfo.Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production"; | ||||
|                     HardwareInfo.UUID = HardwareInfo.MachineInfo.UUID; | ||||
|                     HardwareInfo.Environment = App.HostEnvironment.EnvironmentName; | ||||
|  | ||||
|                     HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat(); | ||||
|                     HardwareInfo.UpdateTime = TimerX.Now; | ||||
|                 } | ||||
|             } | ||||
|             catch | ||||
| @@ -99,9 +97,12 @@ public class HardwareJob : IJob, IHardwareJob | ||||
|             } | ||||
|             try | ||||
|             { | ||||
|                 HardwareInfo.MachineInfo.Refresh(); | ||||
|                 HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat(); | ||||
|                 HardwareInfo.WorkingSet = (Environment.WorkingSet / 1024.0 / 1024.0).ToInt(); | ||||
|                 var machine = MachineInfo.GetCurrent(); | ||||
|                 machine.Refresh(); | ||||
|                 machine.AdaptHardwareInfo(HardwareInfo); | ||||
|                 HardwareInfo.AppRunTotalMinute = (ulong)Runtime.AppTickCount64 / 1000 /60; | ||||
|                 HardwareInfo.SystemRunTotalMinute = (ulong)Runtime.TickCount64 / 1000 /60; | ||||
|                 HardwareInfo.UpdateTime = TimerX.Now; | ||||
|                 error = false; | ||||
|             } | ||||
|             catch (Exception ex) | ||||
| @@ -123,10 +124,10 @@ public class HardwareJob : IJob, IHardwareJob | ||||
|                             { | ||||
|                                 Date = TimerX.Now, | ||||
|                                 DriveUsage = (100 - (HardwareInfo.DriveInfo.TotalFreeSpace * 100.00 / HardwareInfo.DriveInfo.TotalSize)).ToInt(), | ||||
|                                 Battery = (HardwareInfo.MachineInfo.Battery * 100).ToInt(), | ||||
|                                 Battery = (HardwareInfo.Battery * 100).ToInt(), | ||||
|                                 MemoryUsage = (HardwareInfo.WorkingSet), | ||||
|                                 CpuUsage = (HardwareInfo.MachineInfo.CpuRate * 100).ToInt(), | ||||
|                                 Temperature = (HardwareInfo.MachineInfo.Temperature).ToInt(), | ||||
|                                 CpuUsage = (HardwareInfo.CpuRate * 100).ToInt(), | ||||
|                                 Temperature = (HardwareInfo.Temperature).ToInt(), | ||||
|                             }; | ||||
|                             await _db.InsertableT(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false); | ||||
|                             MemoryCache.Remove(CacheKey); | ||||
|   | ||||
| @@ -21,7 +21,7 @@ public class HistoryHardwareInfo | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [SugarColumn(ColumnDescription = "内存")] | ||||
|     public int MemoryUsage { get; set; } | ||||
|     public UInt64 MemoryUsage { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [SugarColumn(ColumnDescription = "CPU使用率")] | ||||
|   | ||||
| @@ -21,7 +21,7 @@ | ||||
|     "UserNoModule": "This account has not been assigned a module. Please contact the administrator", | ||||
|     "UserNull": "User {0} does not exist" | ||||
|   }, | ||||
|    | ||||
|  | ||||
|   "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": { | ||||
|     "UserExpire": "User expired, please login again" | ||||
|   }, | ||||
| @@ -46,12 +46,44 @@ | ||||
|     "FileTypeError": "Not supported format {0}" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Application.HardwareInfo": { | ||||
|     "Environment": "HostEnvironment", | ||||
|     "FrameworkDescription": ".NETFramework", | ||||
|     "OsArchitecture": "System Architecture", | ||||
|     "UpdateTime": "UpdateTime", | ||||
|     "UUID": "UUID" | ||||
|     "DriveInfo": "Current Disk Info", | ||||
|     "AppRunTotalMinute": "AppRunTotalMinute(min)", | ||||
|     "SystemRunTotalMinute": "SystemRunTotalMinute(min)", | ||||
|     "Environment": "Host Environment", | ||||
|     "FrameworkDescription": ".NET Framework", | ||||
|     "OsArchitecture": "OS Architecture", | ||||
|     "OSName": "OS Name", | ||||
|     "OSVersion": "OS Version", | ||||
|     "UUID": "UUID", | ||||
|     "Memory": "Total Memory", | ||||
|     "AvailableMemory": "Available Memory", | ||||
|     "CpuRate": "CPU Usage", | ||||
|     "Battery": "Battery Level", | ||||
|     "Temperature": "Temperature", | ||||
|     "Processor": "Processor Model", | ||||
|     "HighMemoryLoadThreshold": "GC High Memory Threshold", | ||||
|     "TotalAvailableMemory": "GC Total Available Memory", | ||||
|     "HeapSize": "Managed Heap Size", | ||||
|     "TotalMemory": "Managed Heap Used", | ||||
|     "FragmentedBytes": "Managed Heap Fragmented", | ||||
|     "GCAvailableMemory": "GC Available Memory", | ||||
|     "CommittedBytes": "GC Committed Bytes", | ||||
|     "TotalAllocatedBytes": "GC Total Allocated (MB)", | ||||
|     "TotalPauseDurationMs": "GC Total Pause Duration (ms)", | ||||
|     "GcGen0Count": "GC Gen0 Count", | ||||
|     "GcGen1Count": "GC Gen1 Count", | ||||
|     "GcGen2Count": "GC Gen2 Count", | ||||
|     "IsServerGC": "Server GC", | ||||
|     "GCLatencyMode": "GC Latency Mode", | ||||
|     "PinnedObjectsCount": "Pinned Objects Count", | ||||
|     "FinalizationPendingCount": "Finalization Pending Count", | ||||
|     "VirtualMemory": "Virtual Memory", | ||||
|     "PrivateMemory": "Private Memory", | ||||
|     "PeakWorkingSet": "Peak Working Set", | ||||
|     "WorkingSet": "Current Working Set", | ||||
|     "UpdateTime": "Update Time" | ||||
|   }, | ||||
|  | ||||
|   "ThingsGateway.Admin.Application.HardwareJob": { | ||||
|     "GetHardwareInfoFail": "Get Hardwareinfo Fail" | ||||
|   }, | ||||
|   | ||||
| @@ -46,11 +46,42 @@ | ||||
|     "FileTypeError": "不支持 {0} 格式" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Application.HardwareInfo": { | ||||
|     "DriveInfo": "当前磁盘信息", | ||||
|     "AppRunTotalMinute": "软件运行时长(min)", | ||||
|     "SystemRunTotalMinute": "系统运行时长(min)", | ||||
|     "Environment": "主机环境", | ||||
|     "FrameworkDescription": "NET框架", | ||||
|     "FrameworkDescription": ".NET 框架", | ||||
|     "OsArchitecture": "系统架构", | ||||
|     "UpdateTime": "更新时间", | ||||
|     "UUID": "唯一编码" | ||||
|     "OSName": "系统名称", | ||||
|     "OSVersion": "系统版本", | ||||
|     "UUID": "UUID", | ||||
|     "Memory": "系统总内存", | ||||
|     "AvailableMemory": "系统可用内存", | ||||
|     "CpuRate": "CPU 占用率", | ||||
|     "Battery": "电池电量", | ||||
|     "Temperature": "温度", | ||||
|     "Processor": "处理器型号", | ||||
|     "HighMemoryLoadThreshold": "GC 高内存阈值", | ||||
|     "TotalAvailableMemory": "GC 总内存阈值", | ||||
|     "HeapSize": "托管堆容量", | ||||
|     "TotalMemory": "托管对象占用", | ||||
|     "FragmentedBytes": "托管堆碎片", | ||||
|     "GCAvailableMemory": "GC 可用内存", | ||||
|     "CommittedBytes": "GC 提交内存总量", | ||||
|     "TotalAllocatedBytes": "GC 累计分配(MB)", | ||||
|     "TotalPauseDurationMs": "GC 累计暂停时间(ms)", | ||||
|     "GcGen0Count": "GC Gen0 次数", | ||||
|     "GcGen1Count": "GC Gen1 次数", | ||||
|     "GcGen2Count": "GC Gen2 次数", | ||||
|     "IsServerGC": "Server GC", | ||||
|     "GCLatencyMode": "GC 延迟模式", | ||||
|     "PinnedObjectsCount": "固定对象数", | ||||
|     "FinalizationPendingCount": "终结挂起数", | ||||
|     "VirtualMemory": "虚拟内存", | ||||
|     "PrivateMemory": "私有内存", | ||||
|     "PeakWorkingSet": "峰值工作集", | ||||
|     "WorkingSet": "当前工作集", | ||||
|     "UpdateTime": "更新时间" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Application.HardwareJob": { | ||||
|     "GetHardwareInfoFail": "获取硬件信息出错" | ||||
|   | ||||
| @@ -8,13 +8,20 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using Riok.Mapperly.Abstractions; | ||||
|  | ||||
| using ThingsGateway.NewLife; | ||||
|  | ||||
| namespace ThingsGateway.Admin.Application; | ||||
|  | ||||
| [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)] | ||||
| public static partial class AdminMapper | ||||
| { | ||||
|     public static partial HardwareInfo AdaptHardwareInfo(this MachineInfo src); | ||||
|         public static partial void AdaptHardwareInfo(this MachineInfo src, HardwareInfo dto); | ||||
|  | ||||
|     public static partial LoginInput AdaptLoginInput(this OpenApiLoginInput src); | ||||
|     public static partial OpenApiLoginOutput AdaptOpenApiLoginOutput(this LoginOutput src); | ||||
|     public static partial SessionOutput AdaptSessionOutput(this SysUser src); | ||||
|   | ||||
| @@ -20,7 +20,7 @@ namespace ThingsGateway.Admin.Application; | ||||
| /// <typeparam name="TEntry"></typeparam> | ||||
| public class EventService<TEntry> : IEventService<TEntry>, IDisposable | ||||
| { | ||||
|     private ConcurrentDictionary<string, Func<TEntry, Task>> Cache = new(); | ||||
|     private NonBlockingDictionary<string, Func<TEntry, Task>> Cache = new(); | ||||
|  | ||||
|     public void Dispose() | ||||
|     { | ||||
|   | ||||
| @@ -19,12 +19,14 @@ | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="Rougamo.Fody" Version="5.0.1" /> | ||||
| 		<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> | ||||
|   | ||||
| @@ -4,8 +4,8 @@ | ||||
|  | ||||
| <div class="tg-table h-100"> | ||||
|  | ||||
|     <Table TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate" | ||||
|            DataService="DataService" CreateItemCallback="CreateItemCallback!" | ||||
|     <Table Id=@Id TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate" | ||||
|            DataService="DataService" CreateItemCallback="CreateItemCallback!" RenderMode=RenderMode OnColumnCreating=OnColumnCreating | ||||
|            IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" DisableEditButtonCallback="DisableEditButtonCallback" DisableDeleteButtonCallback="DisableDeleteButtonCallback" BeforeShowEditDialogCallback=" BeforeShowEditDialogCallback!" | ||||
|            IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-solid fa-circle-chevron-right" TreeExpandIcon="fa-solid fa-circle-chevron-right fa-rotate-90" IsAutoQueryFirstRender=IsAutoQueryFirstRender | ||||
|            ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton | ||||
| @@ -14,7 +14,7 @@ | ||||
|            ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo | ||||
|            SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText | ||||
|            ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode | ||||
|            ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView | ||||
|            ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView OnColumnVisibleChanged=OnColumnVisibleChanged | ||||
|            FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn | ||||
|            IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval | ||||
|            AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh | ||||
| @@ -41,6 +41,7 @@ | ||||
|            DoubleClickToEdit="DoubleClickToEdit" | ||||
|            OnDoubleClickCellCallback="OnDoubleClickCellCallback" | ||||
|            OnDoubleClickRowCallback="OnDoubleClickRowCallback" | ||||
|            RowContentTemplate="RowContentTemplate" | ||||
|            OnClickRowCallback="OnClickRowCallback"> | ||||
|     </Table> | ||||
| </div> | ||||
|   | ||||
| @@ -13,6 +13,23 @@ namespace ThingsGateway.Admin.Razor; | ||||
| [CascadingTypeParameter(nameof(TItem))] | ||||
| public partial class AdminTable<TItem> where TItem : class, new() | ||||
| { | ||||
|     /// <inheritdoc cref="Table{TItem}.OnColumnVisibleChanged"/> | ||||
|     [Parameter] | ||||
|     public Func<string, bool, Task> OnColumnVisibleChanged { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="Table{TItem}.OnColumnCreating"/> | ||||
|     [Parameter] | ||||
|     public Func<List<ITableColumn>, Task> OnColumnCreating { get; set; } | ||||
|     /// <inheritdoc cref="Table{TItem}.RenderMode"/> | ||||
|     [Parameter] | ||||
|     public TableRenderMode RenderMode { get; set; } | ||||
|  | ||||
|     public List<ITableColumn> Columns => Instance?.Columns; | ||||
|  | ||||
|     public IEnumerable<ITableColumn> GetVisibleColumns => Instance?.GetVisibleColumns(); | ||||
|     public List<TItem> Rows => Instance?.Rows; | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/> | ||||
|     [Parameter] | ||||
|     public EventCallback<List<TItem>> SelectedRowsChanged { get; set; } | ||||
| @@ -40,6 +57,10 @@ public partial class AdminTable<TItem> where TItem : class, new() | ||||
|     /// <inheritdoc cref="Table{TItem}.OnDoubleClickRowCallback"/> | ||||
|     [Parameter] | ||||
|     public Func<TItem, Task>? OnDoubleClickRowCallback { get; set; } | ||||
|     /// <inheritdoc cref="Table{TItem}.RowContentTemplate"/> | ||||
|     [Parameter] | ||||
|     public RenderFragment<TableRowContext<TItem>>? RowContentTemplate { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="Table{TItem}.OnClickRowCallback"/> | ||||
|     [Parameter] | ||||
|     public Func<TItem, Task>? OnClickRowCallback { get; set; } | ||||
| @@ -146,6 +167,9 @@ public partial class AdminTable<TItem> where TItem : class, new() | ||||
|     [Parameter] | ||||
|     public IDataService<TItem> DataService { get; set; } | ||||
|  | ||||
|     [Parameter] | ||||
|     public string? Id { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="Table{TItem}.CreateItemCallback"/> | ||||
|     [Parameter] | ||||
|     public Func<TItem> CreateItemCallback { get; set; } | ||||
|   | ||||
| @@ -22,6 +22,9 @@ | ||||
|     "SetDefaultModule": "Set as default module" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Razor.HardwareInfoPage": { | ||||
|     "GCMemoryInfoConfig": "GCMemoryInfoConfig(MB)", | ||||
|     "GCMemoryInfo": "GCMemoryInfo(MB)", | ||||
|     "GCAnalyzeInfo": "GCAnalyzeInfo", | ||||
|     "AvailableDisk": "Available Disk", | ||||
|     "AvailableMemory": "Available Memory", | ||||
|     "CpuUsage": "CPU Usage", | ||||
|   | ||||
| @@ -22,6 +22,10 @@ | ||||
|     "SetDefaultModule": "设置为默认模块" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Razor.HardwareInfoPage": { | ||||
|     "GCMemoryInfoConfig": "GC配置信息(MB)", | ||||
|     "GCMemoryInfo": "GC内存信息(MB)", | ||||
|     "GCAnalyzeInfo": "GC统计", | ||||
|  | ||||
|     "AvailableDisk": "可用磁盘", | ||||
|     "AvailableMemory": "可用内存", | ||||
|     "CpuUsage": "CPU使用率", | ||||
|   | ||||
| @@ -5,131 +5,189 @@ | ||||
| @using ThingsGateway.Admin.Application | ||||
| @namespace ThingsGateway.Admin.Razor | ||||
| <div class="row g-2 mx-1 form-inline"> | ||||
|  | ||||
|     <div class="col-12 col-md-4"> | ||||
|     <div class="col-12 col-md-12"> | ||||
|  | ||||
|         <Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary"> | ||||
|             <HeaderTemplate> | ||||
|                 @Localizer["SystemInfo"] | ||||
|             </HeaderTemplate> | ||||
|  | ||||
|             <BodyTemplate> | ||||
|                 <EditorFormObject IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow="1" RowType=RowType.Inline LabelWidth="160"> | ||||
|                     <FieldItems> | ||||
|                         <EditorItem @bind-Field="@context.MachineInfo"> | ||||
|                             <EditTemplate Context="value"> | ||||
|                                 <div class="col-12  col-md-12"> | ||||
|                                     <Display @bind-Value="@value.MachineInfo.OSName" DisplayText="@Localizer[nameof(value.MachineInfo.OSName)]" ShowTooltip ShowLabel=true> | ||||
|                                     </Display> | ||||
|                                 </div> | ||||
|                                 <div class="col-12  col-md-12"> | ||||
|                                     <Display @bind-Value="@value.MachineInfo.OSVersion" DisplayText="@Localizer[nameof(value.MachineInfo.OSVersion)]" ShowTooltip ShowLabel=true> | ||||
|                                     </Display> | ||||
|                                 </div> | ||||
|                             </EditTemplate> | ||||
|                         </EditorItem> | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.UUID"> | ||||
|                             <EditTemplate Context="value"> | ||||
|                                 <div class="col-12  col-md-12"> | ||||
|                                     <Display @bind-Value="@value.UUID" ShowTooltip ShowLabel=true> | ||||
|                                     </Display> | ||||
|                                 </div> | ||||
|  | ||||
|                             </EditTemplate> | ||||
|                         </EditorItem> | ||||
|                         <EditorItem @bind-Field="@context.DriveInfo" Ignore=true> | ||||
|                         </EditorItem> | ||||
|                     </FieldItems> | ||||
|                 </EditorFormObject> | ||||
|  | ||||
|             </BodyTemplate> | ||||
|         </Card> | ||||
|     </div> | ||||
|     <div class="col-12 col-md-8"> | ||||
|         <Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary"> | ||||
|             <HeaderTemplate> | ||||
|                 @Localizer["HardwareInfo"] | ||||
|             </HeaderTemplate> | ||||
|  | ||||
|             <BodyTemplate> | ||||
|                 <div class="d-flex align-items-center justify-content-center w-100 h-100" > | ||||
|  | ||||
|                     <div class="row g-2 mx-1 form-inline d-flex justify-content-center w-100"> | ||||
|                         <div class="col-12 col-md-4  justify-content-center h-100" > | ||||
|                             @{ | ||||
|                                 var item = HardwareJob.HardwareInfo.MachineInfo.CpuRate; | ||||
|                             } | ||||
|                             <div class="d-flex flex-column align-items-center"> | ||||
|                                 <Circle Width="200" class="m-3" | ||||
|                                         Value=@((int)(item*100<=100?item*100:100)) | ||||
|                                         Color=@((item*100>70?Color.Warning:Color.Success)) | ||||
|                                         StrokeWidth="4" ShowProgress=false> | ||||
|                                     <div class="circle-hardware"> | ||||
|                                         <span> | ||||
|                                             @Localizer["CpuUsage"] <i> @((item * 100).ToString("F0") + " %")</i> | ||||
|                                         </span> | ||||
|                                     </div> | ||||
|                                 </Circle> | ||||
|                                 <div class="mt-1"> | ||||
|                                     <span>@(HardwareJob.HardwareInfo.MachineInfo.Processor)</span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-12 col-md-4 justify-content-center h-100"> | ||||
|                             @{ | ||||
|                                 var availableMemory = HardwareJob.HardwareInfo.MachineInfo.AvailableMemory; | ||||
|                                 var memory = HardwareJob.HardwareInfo.MachineInfo.Memory; | ||||
|                             } | ||||
|                             <div class="d-flex flex-column align-items-center "> | ||||
|                                 <Circle Width="200" class="m-3" | ||||
|                                         Value=@((int)(memory>availableMemory?100 - (availableMemory * 100.00 / memory):100)) | ||||
|                                         Color=@((availableMemory * 100.00 / memory<20?Color.Warning:Color.Success)) | ||||
|                                         StrokeWidth="4"> | ||||
|                                     <div class="circle-hardware"> | ||||
|                                         <h6>   @((100 - (availableMemory * 100.00 / memory)).ToString("F2") + " %")  </h6> | ||||
|  | ||||
|                                         <span>   @Localizer["WorkingSet"] <i> @(HardwareJob.HardwareInfo.WorkingSet + " MB")</i></span> | ||||
|                                         <span>   @Localizer["AvailableMemory"] <i> @((availableMemory / 1024.00 / 1024).ToString("F2") + " GB")</i></span> | ||||
|                                         <span>   @Localizer["TotalMemory"] <i> @((memory / 1024.00 / 1024).ToString("F2") + " GB")</i></span> | ||||
|  | ||||
|                                     </div> | ||||
|                                 </Circle> | ||||
|                                 <div class="mt-1"> | ||||
|                                     <span>  @Localizer["MemoryUsage"] </span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-12 col-md-4 justify-content-center h-100"> | ||||
|                             @{ | ||||
|                                 var totalFreeSpace = HardwareJob.HardwareInfo.DriveInfo.TotalFreeSpace; | ||||
|                                 var totalSize = HardwareJob.HardwareInfo.DriveInfo.TotalSize; | ||||
|                             } | ||||
|                             <div class="d-flex flex-column align-items-center "> | ||||
|                                 <Circle Width="200" class="m-3" | ||||
|                                         Value=@((int)(totalSize>totalFreeSpace?100 - (totalFreeSpace * 100.00 / totalSize):100)) | ||||
|                                         Color=@((totalFreeSpace * 100.00 / totalSize<20?Color.Warning:Color.Success)) | ||||
|                                         StrokeWidth="4"> | ||||
|                                     <div class="circle-hardware"> | ||||
|                                         <h6>   @((100 - (totalFreeSpace * 100.00 / totalSize)).ToString("F2") + " %")</h6> | ||||
|                                         <span>   @(HardwareJob.HardwareInfo.DriveInfo.VolumeLabel) </span> | ||||
|                                         <span>   @Localizer["AvailableDisk"] <i> @((totalFreeSpace / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span> | ||||
|                                         <span>   @Localizer["TotalDisk"] <i> @((totalSize / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span> | ||||
|                                     </div> | ||||
|                                 </Circle> | ||||
|                                 <div class="mt-1"> | ||||
|                                     <span>  @Localizer["DiskUsage"] </span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 <div class="d-flex justify-content-between align-items-center w-100"> | ||||
|                     <span>@Localizer["SystemInfo"]</span> | ||||
|                     <small class="text-muted"> | ||||
|                         @HardwareJob.HardwareInfo.UpdateTime.ToString("yyyy-MM-dd HH:mm:ss") | ||||
|                     </small> | ||||
|                 </div> | ||||
|             </HeaderTemplate> | ||||
|  | ||||
|             <BodyTemplate> | ||||
|                 <EditorForm class="mt-3" AutoGenerateAllItem="false" IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow=3 RowType=RowType.Inline LabelWidth=300> | ||||
|                     <FieldItems> | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.OSName" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.OsArchitecture" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.OSVersion" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.Environment" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.FrameworkDescription" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.UUID" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.SystemRunTotalMinute" GroupName="@Localizer["SystemInfo"]" /> | ||||
|                         <EditorItem @bind-Field="@context.AppRunTotalMinute" GroupName="@Localizer["SystemInfo"]" /> | ||||
|  | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.Memory" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 /> | ||||
|                         <EditorItem @bind-Field="@context.TotalAvailableMemory" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 /> | ||||
|                         <EditorItem @bind-Field="@context.HighMemoryLoadThreshold" GroupName="@Localizer["GCMemoryConfig"]" GroupOrder=2 /> | ||||
|  | ||||
|                         <EditorItem TValue="bool" TModel="HardwareInfo" @bind-Field="@context.IsServerGC" GroupName="@Localizer["GCMemoryConfig"]" GroupOrder=2> | ||||
|                             <EditTemplate Context="value"> | ||||
|                                 <div class="col-12"> | ||||
|                                     <h6></h6> | ||||
|                                 </div> | ||||
|                             </EditTemplate> | ||||
|                         </EditorItem> | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.IsServerGC" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 /> | ||||
|                         <EditorItem @bind-Field="@context.GCLatencyMode" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 /> | ||||
|  | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.WorkingSet" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.PrivateMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.PeakWorkingSet" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.HeapSize" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3> | ||||
|                             <EditTemplate Context="value"> | ||||
|                                 <div class="col-12"> | ||||
|                                     <h6></h6> | ||||
|                                 </div> | ||||
|                             </EditTemplate> | ||||
|                         </EditorItem> | ||||
|                         <EditorItem @bind-Field="@context.HeapSize" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.TotalMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.FragmentedBytes" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.CommittedBytes" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|  | ||||
|                         <EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.AvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3> | ||||
|                             <EditTemplate Context="value"> | ||||
|                                 <div class="col-12"> | ||||
|                                     <h6></h6> | ||||
|                                 </div> | ||||
|                             </EditTemplate> | ||||
|                         </EditorItem> | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.AvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.GCAvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|                         <EditorItem @bind-Field="@context.VirtualMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 /> | ||||
|  | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.GcGen0Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 /> | ||||
|                         <EditorItem @bind-Field="@context.GcGen1Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 /> | ||||
|                         <EditorItem @bind-Field="@context.GcGen2Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 /> | ||||
|                         <EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.TotalAllocatedBytes" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4> | ||||
|                             <EditTemplate Context="value"> | ||||
|                                 <div class="col-12"> | ||||
|                                     <h6></h6> | ||||
|                                 </div> | ||||
|                             </EditTemplate> | ||||
|                         </EditorItem> | ||||
|  | ||||
|                         <EditorItem @bind-Field="@context.TotalAllocatedBytes" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 /> | ||||
|                         <EditorItem @bind-Field="@context.TotalPauseDurationMs" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 /> | ||||
|  | ||||
|                     </FieldItems> | ||||
|                 </EditorForm> | ||||
|  | ||||
|  | ||||
|  | ||||
|             </BodyTemplate> | ||||
|         </Card> | ||||
|  | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| <div class="row g-2 mx-1 form-inline mt-2"> | ||||
|  | ||||
|     <div class="col-12 col-md-12"> | ||||
|         <Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary"> | ||||
|             <HeaderTemplate> | ||||
|                 @Localizer["HardwareInfo"] | ||||
|             </HeaderTemplate> | ||||
|  | ||||
|             <BodyTemplate> | ||||
|                 <div class="d-flex align-items-center justify-content-center w-100 h-100"> | ||||
|                     <div class="row g-2 mx-1 form-inline d-flex justify-content-center w-100"> | ||||
|                         <!-- ✅ CPU 使用率 --> | ||||
|                         <div class="col-12 col-md-4 justify-content-center h-100"> | ||||
|                             @{ | ||||
|                                 var cpuUsage = HardwareJob.HardwareInfo.CpuRate; | ||||
|                             } | ||||
|                             <div class="d-flex flex-column align-items-center"> | ||||
|                                 <Circle Width="200" class="m-3" | ||||
|                                         Value=@((int)(cpuUsage*100<=100?cpuUsage*100:100)) | ||||
|                                         Color=@((cpuUsage*100>70?Color.Warning:Color.Success)) | ||||
|                                         StrokeWidth="4" ShowProgress=false> | ||||
|                                     <div class="circle-hardware"> | ||||
|                                         <span> | ||||
|                                             @Localizer["CpuUsage"] <i>@((cpuUsage * 100).ToString("F0") + " %")</i> | ||||
|                                         </span> | ||||
|                                     </div> | ||||
|                                 </Circle> | ||||
|                                 <div class="mt-1"> | ||||
|                                     <span>@(HardwareJob.HardwareInfo.Processor)</span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- ✅ 内存使用率 --> | ||||
|                         <div class="col-12 col-md-4 justify-content-center h-100"> | ||||
|                             @{ | ||||
|                                 var availableMemory = HardwareJob.HardwareInfo.AvailableMemory; | ||||
|                                 var memory = HardwareJob.HardwareInfo.Memory; | ||||
|                             } | ||||
|                             <div class="d-flex flex-column align-items-center"> | ||||
|                                 <Circle Width="200" class="m-3" | ||||
|                                         Value=@((int)(memory>availableMemory?100 - (availableMemory * 100.00 / memory):100)) | ||||
|                                         Color=@((availableMemory * 100.00 / memory<20?Color.Warning:Color.Success)) | ||||
|                                         StrokeWidth="4"> | ||||
|                                     <div class="circle-hardware"> | ||||
|                                         <h6>@((100 - (availableMemory * 100.00 / memory)).ToString("F2") + " %")</h6> | ||||
|                                         <span>@Localizer["WorkingSet"] <i>@(HardwareJob.HardwareInfo.WorkingSet + " MB")</i></span> | ||||
|                                         <span>@Localizer["AvailableMemory"] <i>@((availableMemory / 1024.00).ToString("F2") + " GB")</i></span> | ||||
|                                         <span>@Localizer["TotalMemory"] <i>@((memory / 1024.00).ToString("F2") + " GB")</i></span> | ||||
|                                     </div> | ||||
|                                 </Circle> | ||||
|                                 <div class="mt-1"> | ||||
|                                     <span>@Localizer["MemoryUsage"]</span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- ✅ 磁盘使用率 --> | ||||
|                         <div class="col-12 col-md-4 justify-content-center h-100"> | ||||
|                             @{ | ||||
|                                 var totalFreeSpace = HardwareJob.HardwareInfo.DriveInfo.TotalFreeSpace; | ||||
|                                 var totalSize = HardwareJob.HardwareInfo.DriveInfo.TotalSize; | ||||
|                             } | ||||
|                             <div class="d-flex flex-column align-items-center"> | ||||
|                                 <Circle Width="200" class="m-3" | ||||
|                                         Value=@((int)(totalSize>totalFreeSpace?100 - (totalFreeSpace * 100.00 / totalSize):100)) | ||||
|                                         Color=@((totalFreeSpace * 100.00 / totalSize<20?Color.Warning:Color.Success)) | ||||
|                                         StrokeWidth="4"> | ||||
|                                     <div class="circle-hardware"> | ||||
|                                         <h6>@((100 - (totalFreeSpace * 100.00 / totalSize)).ToString("F2") + " %")</h6> | ||||
|                                         <span>@(HardwareJob.HardwareInfo.DriveInfo.VolumeLabel)</span> | ||||
|                                         <span>@Localizer["AvailableDisk"] <i>@((totalFreeSpace / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span> | ||||
|                                         <span>@Localizer["TotalDisk"] <i>@((totalSize / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span> | ||||
|                                     </div> | ||||
|                                 </Circle> | ||||
|                                 <div class="mt-1"> | ||||
|                                     <span>@Localizer["DiskUsage"]</span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </BodyTemplate> | ||||
|         </Card> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
|  | ||||
| <div class="row g-2 mx-1 form-inline"> | ||||
|  | ||||
|     <div class="col-12 col-md-12"> | ||||
| @@ -140,7 +198,7 @@ | ||||
|             </HeaderTemplate> | ||||
|  | ||||
|             <BodyTemplate> | ||||
|                 <Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="()=>{chartInit=true;return Task.CompletedTask;}" /> | ||||
|                 <Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="() => { chartInit = true; return Task.CompletedTask; }" /> | ||||
|             </BodyTemplate> | ||||
|         </Card> | ||||
|     </div> | ||||
|   | ||||
| @@ -72,7 +72,7 @@ public partial class HardwareInfoPage : IDisposable | ||||
|             ChartDataSource.Options.Title = Localizer[nameof(HistoryHardwareInfo)]; | ||||
|             ChartDataSource.Options.X.Title = Localizer["DateTime"]; | ||||
|             ChartDataSource.Options.Y.Title = Localizer["Data"]; | ||||
|             ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd HH:mm zz")); | ||||
|             ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd-HH:mm")); | ||||
|             ChartDataSource.Data.Add(new ChartDataset() | ||||
|             { | ||||
|                 Tension = 0.4f, | ||||
| @@ -116,7 +116,7 @@ public partial class HardwareInfoPage : IDisposable | ||||
|         else | ||||
|         { | ||||
|             var hisHardwareInfos = await HardwareJob.GetHistoryHardwareInfos(); | ||||
|             ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd HH:mm zz")); | ||||
|             ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd-HH:mm")); | ||||
|             ChartDataSource.Data[0].Data = hisHardwareInfos.Select(a => (object)a.CpuUsage); | ||||
|             ChartDataSource.Data[1].Data = hisHardwareInfos.Select(a => (object)a.MemoryUsage); | ||||
|             ChartDataSource.Data[2].Data = hisHardwareInfos.Select(a => (object)a.DriveUsage); | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.1" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.4" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.3" /> | ||||
|   | ||||
| @@ -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,15 +15,11 @@ | ||||
| 		<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> | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| @@ -477,7 +477,7 @@ public class ConcurrentList<T> : IList<T>, IReadOnlyList<T> | ||||
|     { | ||||
|         lock (((ICollection)m_list).SyncRoot) | ||||
|         { | ||||
|             return m_list.IndexOf(item); | ||||
|             return m_list.LastIndexOf(item); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -82,7 +82,7 @@ public static class ObjectExtensions | ||||
|     /// <typeparam name="T"></typeparam> | ||||
|     /// <param name="dic">字典</param> | ||||
|     /// <param name="newDic">新字典</param> | ||||
|     internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic) | ||||
|     internal static void AddOrUpdate<T>(this NonBlockingDictionary<string, T> dic, Dictionary<string, T> newDic) | ||||
|     { | ||||
|         foreach (var (key, value) in newDic) | ||||
|         { | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
|  | ||||
| @@ -105,37 +104,6 @@ internal class CacheManager | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置 App 开始时间 | ||||
|     /// </summary> | ||||
|     public void SetStartTime() => SetStartTime(DateTimeOffset.Now); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置 App 开始时间 | ||||
|     /// </summary> | ||||
|     private void SetStartTime(DateTimeOffset startDateTimeOffset) | ||||
|     { | ||||
|         GetOrCreate("BootstrapBlazor_StartTime", entry => | ||||
|         { | ||||
|             entry.Priority = CacheItemPriority.NeverRemove; | ||||
|             return startDateTimeOffset; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取 App 开始时间 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public DateTimeOffset GetStartTime() | ||||
|     { | ||||
|         var ret = DateTimeOffset.MinValue; | ||||
|         if (Cache.TryGetValue("BootstrapBlazor_StartTime", out var v) && v is DateTimeOffset d) | ||||
|         { | ||||
|             ret = d; | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获得 缓存数量 | ||||
|     /// </summary> | ||||
| @@ -236,7 +204,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 +212,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 +221,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 +237,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 +313,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 | ||||
|   | ||||
| @@ -66,7 +66,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); | ||||
|                     if (Logger?.IsEnabled(LogLevel.Error) == true) | ||||
|                         Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); | ||||
|                 } | ||||
|                 return ret; | ||||
|             } | ||||
| @@ -176,7 +177,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | ||||
|         localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name); | ||||
|         if (jsonLocalizationOptions.IgnoreLocalizerMissing == false) | ||||
|         { | ||||
|             Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); | ||||
|             if (Logger?.IsEnabled(LogLevel.Information) == true) | ||||
|                 Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); | ||||
|         } | ||||
|         _missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}"); | ||||
|     } | ||||
|   | ||||
| @@ -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.1" /> | ||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.11.4" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -205,7 +205,7 @@ public static class ObjectExtensions | ||||
|     /// <typeparam name="T"></typeparam> | ||||
|     /// <param name="dic">字典</param> | ||||
|     /// <param name="newDic">新字典</param> | ||||
|     internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic) | ||||
|     internal static void AddOrUpdate<T>(this NonBlockingDictionary<string, T> dic, Dictionary<string, T> newDic) | ||||
|     { | ||||
|         foreach (var (key, value) in newDic) | ||||
|         { | ||||
|   | ||||
| @@ -94,7 +94,7 @@ public static class AspNetCoreBuilderServiceCollectionExtensions | ||||
|     /// <param name="mvcBuilder"></param> | ||||
|     /// <param name="configure"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action<ConcurrentDictionary<Type, Type>> configure = default) | ||||
|     public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action<NonBlockingDictionary<Type, Type>> configure = default) | ||||
|     { | ||||
|         mvcBuilder.Services.AddFromConvertBinding(configure); | ||||
|  | ||||
| @@ -107,13 +107,13 @@ public static class AspNetCoreBuilderServiceCollectionExtensions | ||||
|     /// <param name="services"></param> | ||||
|     /// <param name="configure"></param> | ||||
|     /// <returns></returns> | ||||
|     public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action<ConcurrentDictionary<Type, Type>> configure = default) | ||||
|     public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action<NonBlockingDictionary<Type, Type>> configure = default) | ||||
|     { | ||||
|         // 非 Web 环境跳过注册 | ||||
|         if (App.WebHostEnvironment == default) return services; | ||||
|  | ||||
|         // 定义模型绑定转换器集合 | ||||
|         var modelBinderConverts = new ConcurrentDictionary<Type, Type>(); | ||||
|         var modelBinderConverts = new NonBlockingDictionary<Type, Type>(); | ||||
|         modelBinderConverts.TryAdd(typeof(DateTime), typeof(DateTimeModelConvertBinder)); | ||||
|         modelBinderConverts.TryAdd(typeof(DateTimeOffset), typeof(DateTimeOffsetModelConvertBinder)); | ||||
|  | ||||
|   | ||||
| @@ -27,13 +27,13 @@ public class FromConvertBinder : IModelBinder | ||||
|     /// <summary> | ||||
|     /// 定义模型绑定转换器集合 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts; | ||||
|     private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="modelBinderConverts">定义模型绑定转换器集合</param> | ||||
|     public FromConvertBinder(ConcurrentDictionary<Type, Type> modelBinderConverts) | ||||
|     public FromConvertBinder(NonBlockingDictionary<Type, Type> modelBinderConverts) | ||||
|     { | ||||
|         _modelBinderConverts = modelBinderConverts; | ||||
|     } | ||||
|   | ||||
| @@ -28,13 +28,13 @@ public class FromConvertBinderProvider : IModelBinderProvider | ||||
|     /// <summary> | ||||
|     /// 定义模型绑定转换器集合 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts; | ||||
|     private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     /// <param name="modelBinderConverts">定义模型绑定转换器集合</param> | ||||
|     public FromConvertBinderProvider(ConcurrentDictionary<Type, Type> modelBinderConverts) | ||||
|     public FromConvertBinderProvider(NonBlockingDictionary<Type, Type> modelBinderConverts) | ||||
|     { | ||||
|         _modelBinderConverts = modelBinderConverts; | ||||
|     } | ||||
|   | ||||
| @@ -40,7 +40,7 @@ public static class DataValidator | ||||
|     /// <summary> | ||||
|     /// 验证类型正则表达式 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<string, ValidationItemMetadataAttribute> ValidationItemMetadatas; | ||||
|     private static readonly NonBlockingDictionary<string, ValidationItemMetadataAttribute> ValidationItemMetadatas; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
| @@ -57,7 +57,7 @@ public static class DataValidator | ||||
|         ValidationItemMetadatas = GetValidationValidationItemMetadatas(); | ||||
|  | ||||
|         // 缓存所有正则表达式 | ||||
|         GetValidationTypeValidationItemMetadataCached = new ConcurrentDictionary<object, (string, ValidationItemMetadataAttribute)>(); | ||||
|         GetValidationTypeValidationItemMetadataCached = new NonBlockingDictionary<object, (string, ValidationItemMetadataAttribute)>(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -203,7 +203,7 @@ public static class DataValidator | ||||
|     /// <summary> | ||||
|     /// 获取验证类型验证Item集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<object, (string, ValidationItemMetadataAttribute)> GetValidationTypeValidationItemMetadataCached; | ||||
|     private static readonly NonBlockingDictionary<object, (string, ValidationItemMetadataAttribute)> GetValidationTypeValidationItemMetadataCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取验证类型正则表达式(需要缓存) | ||||
| @@ -267,9 +267,9 @@ public static class DataValidator | ||||
|     /// 获取验证类型所有有效的正则表达式 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private static ConcurrentDictionary<string, ValidationItemMetadataAttribute> GetValidationValidationItemMetadatas() | ||||
|     private static NonBlockingDictionary<string, ValidationItemMetadataAttribute> GetValidationValidationItemMetadatas() | ||||
|     { | ||||
|         var vaidationItems = new ConcurrentDictionary<string, ValidationItemMetadataAttribute>(); | ||||
|         var vaidationItems = new NonBlockingDictionary<string, ValidationItemMetadataAttribute>(); | ||||
|  | ||||
|         // 查找所有 [ValidationMessageType] 类型中的 [ValidationMessage] 消息定义 | ||||
|         var customErrorMessages = ValidationMessageTypes.SelectMany(u => u.GetFields() | ||||
|   | ||||
| @@ -353,7 +353,7 @@ public static class DependencyInjectionServiceCollectionExtensions | ||||
|     /// <summary> | ||||
|     /// 类型名称集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<string, Type> TypeNamedCollection; | ||||
|     private static readonly NonBlockingDictionary<string, Type> TypeNamedCollection; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 创建代理方法 | ||||
| @@ -374,7 +374,7 @@ public static class DependencyInjectionServiceCollectionExtensions | ||||
|         GlobalServiceProxyType = App.EffectiveTypes | ||||
|             .FirstOrDefault(u => typeof(AspectDispatchProxy).IsAssignableFrom(u) && typeof(IGlobalDispatchProxy).IsAssignableFrom(u) && u.IsClass && !u.IsInterface && !u.IsAbstract); | ||||
|  | ||||
|         TypeNamedCollection = new ConcurrentDictionary<string, Type>(); | ||||
|         TypeNamedCollection = new NonBlockingDictionary<string, Type>(); | ||||
|         DispatchCreateMethod = typeof(AspectDispatchProxy).GetMethod(nameof(AspectDispatchProxy.Create)); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -28,21 +28,21 @@ internal static class Penetrates | ||||
|     /// <summary> | ||||
|     /// 请求动词映射字典 | ||||
|     /// </summary> | ||||
|     internal static ConcurrentDictionary<string, string> VerbToHttpMethods { get; private set; } | ||||
|     internal static NonBlockingDictionary<string, string> VerbToHttpMethods { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 控制器排序集合 | ||||
|     /// </summary> | ||||
|     internal static ConcurrentDictionary<string, (string, int, Type)> ControllerOrderCollection { get; set; } | ||||
|     internal static NonBlockingDictionary<string, (string, int, Type)> ControllerOrderCollection { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
|     /// </summary> | ||||
|     static Penetrates() | ||||
|     { | ||||
|         ControllerOrderCollection = new ConcurrentDictionary<string, (string, int, Type)>(); | ||||
|         ControllerOrderCollection = new NonBlockingDictionary<string, (string, int, Type)>(); | ||||
|  | ||||
|         VerbToHttpMethods = new ConcurrentDictionary<string, string> | ||||
|         VerbToHttpMethods = new NonBlockingDictionary<string, string> | ||||
|         { | ||||
|             ["post"] = "POST", | ||||
|             ["add"] = "POST", | ||||
| @@ -67,13 +67,13 @@ internal static class Penetrates | ||||
|             ["patch"] = "PATCH" | ||||
|         }; | ||||
|  | ||||
|         //IsApiControllerCached = new ConcurrentDictionary<Type, bool>(); | ||||
|         //IsApiControllerCached = new NonBlockingDictionary<Type, bool>(); | ||||
|     } | ||||
|  | ||||
|     ///// <summary> | ||||
|     ///// <see cref="IsApiController(Type)"/> 缓存集合 | ||||
|     ///// </summary> | ||||
|     //private static readonly ConcurrentDictionary<Type, bool> IsApiControllerCached; | ||||
|     //private static readonly NonBlockingDictionary<Type, bool> IsApiControllerCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否是Api控制器 | ||||
|   | ||||
| @@ -61,7 +61,7 @@ internal sealed class EventBusHostedService : BackgroundService | ||||
|     /// <summary> | ||||
|     /// 事件处理程序集合 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<EventHandlerWrapper, EventHandlerWrapper> _eventHandlers = new(); | ||||
|     private readonly NonBlockingDictionary<EventHandlerWrapper, EventHandlerWrapper> _eventHandlers = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 构造函数 | ||||
| @@ -295,7 +295,8 @@ internal sealed class EventBusHostedService : BackgroundService | ||||
|                         , retryAction: (total, times) => | ||||
|                         { | ||||
|                             // 输出重试日志 | ||||
|                             _logger.LogWarning("Retrying {times}/{total} times for {EventId}", times, total, eventSource.EventId); | ||||
|                             if (_logger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning) == true) | ||||
|                                 _logger.LogWarning("Retrying {times}/{total} times for {EventId}", times, total, eventSource.EventId); | ||||
|                         }).ConfigureAwait(false); | ||||
|                     } | ||||
|                     else | ||||
|   | ||||
| @@ -31,7 +31,7 @@ public static class Oops | ||||
|     /// <summary> | ||||
|     /// 方法错误异常特性 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<MethodBase, MethodIfException> _errorMethods; | ||||
|     private static readonly NonBlockingDictionary<MethodBase, MethodIfException> _errorMethods; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 错误代码类型 | ||||
| @@ -41,7 +41,7 @@ public static class Oops | ||||
|     /// <summary> | ||||
|     /// 错误消息字典 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<string, string> _errorCodeMessages; | ||||
|     private static readonly NonBlockingDictionary<string, string> _errorCodeMessages; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 友好异常设置 | ||||
| @@ -53,7 +53,7 @@ public static class Oops | ||||
|     /// </summary> | ||||
|     static Oops() | ||||
|     { | ||||
|         _errorMethods = new ConcurrentDictionary<MethodBase, MethodIfException>(); | ||||
|         _errorMethods = new NonBlockingDictionary<MethodBase, MethodIfException>(); | ||||
|         _friendlyExceptionSettings = App.GetConfig<FriendlyExceptionSettingsOptions>("FriendlyExceptionSettings", true); | ||||
|         _errorCodeTypes = GetErrorCodeTypes(); | ||||
|         _errorCodeMessages = GetErrorCodeMessages(); | ||||
| @@ -258,9 +258,9 @@ public static class Oops | ||||
|     /// 获取所有错误消息 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private static ConcurrentDictionary<string, string> GetErrorCodeMessages() | ||||
|     private static NonBlockingDictionary<string, string> GetErrorCodeMessages() | ||||
|     { | ||||
|         var defaultErrorCodeMessages = new ConcurrentDictionary<string, string>(); | ||||
|         var defaultErrorCodeMessages = new NonBlockingDictionary<string, string>(); | ||||
|  | ||||
|         // 查找所有 [ErrorCodeType] 类型中的 [ErrorCodeMetadata] 元数据定义 | ||||
|         var errorCodeMessages = _errorCodeTypes.SelectMany(u => u.GetFields().Where(u => u.IsDefined(typeof(ErrorCodeItemMetadataAttribute)))) | ||||
|   | ||||
| @@ -23,7 +23,7 @@ public static class ILoggerExtensions | ||||
|     /// 设置日志上下文 | ||||
|     /// </summary> | ||||
|     /// <param name="logger"></param> | ||||
|     /// <param name="properties">建议使用 ConcurrentDictionary 类型</param> | ||||
|     /// <param name="properties">建议使用 NonBlockingDictionary 类型</param> | ||||
|     /// <returns></returns> | ||||
|     public static IDisposable ScopeContext(this ILogger logger, IDictionary<string, object> properties) | ||||
|     { | ||||
|   | ||||
| @@ -82,7 +82,7 @@ public static class StringLoggingExtensions | ||||
|     /// 配置日志上下文 | ||||
|     /// </summary> | ||||
|     /// <param name="message"></param> | ||||
|     /// <param name="properties">建议使用 ConcurrentDictionary 类型</param> | ||||
|     /// <param name="properties">建议使用 NonBlockingDictionary 类型</param> | ||||
|     /// <returns></returns> | ||||
|     public static StringLoggingPart ScopeContext(this string message, IDictionary<string, object> properties) | ||||
|     { | ||||
|   | ||||
| @@ -44,7 +44,7 @@ public sealed class FileLoggerProvider : ILoggerProvider, ISupportExternalScope | ||||
|     /// 记录日志所有滚动文件名 | ||||
|     /// </summary> | ||||
|     /// <remarks>只有 MaxRollingFiles 和 FileSizeLimitBytes 大于 0 有效</remarks> | ||||
|     internal readonly ConcurrentDictionary<string, FileInfo> _rollingFileNames = new(); | ||||
|     internal readonly NonBlockingDictionary<string, FileInfo> _rollingFileNames = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 文件日志写入器 | ||||
|   | ||||
| @@ -94,7 +94,7 @@ public sealed partial class StringLoggingPart | ||||
|     /// <summary> | ||||
|     /// 配置日志上下文 | ||||
|     /// </summary> | ||||
|     /// <param name="properties">建议使用 ConcurrentDictionary 类型</param> | ||||
|     /// <param name="properties">建议使用 NonBlockingDictionary 类型</param> | ||||
|     /// <returns></returns> | ||||
|     public StringLoggingPart ScopeContext(IDictionary<string, object> properties) | ||||
|     { | ||||
|   | ||||
| @@ -57,7 +57,7 @@ public static class Log | ||||
|     /// <summary> | ||||
|     /// 配置日志上下文 | ||||
|     /// </summary> | ||||
|     /// <param name="properties">建议使用 ConcurrentDictionary 类型</param> | ||||
|     /// <param name="properties">建议使用 NonBlockingDictionary 类型</param> | ||||
|     /// <returns></returns> | ||||
|     public static (ILogger logger, IDisposable scope) ScopeContext(IDictionary<string, object> properties) | ||||
|     { | ||||
|   | ||||
| @@ -21,7 +21,7 @@ internal sealed class JobCancellationToken : IJobCancellationToken | ||||
|     /// <summary> | ||||
|     /// 取消作业执行 Token 集合 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<string, CancellationTokenSource> _cancellationTokenSources; | ||||
|     private readonly NonBlockingDictionary<string, CancellationTokenSource> _cancellationTokenSources; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 作业调度器日志服务 | ||||
|   | ||||
| @@ -167,7 +167,7 @@ public partial class JobDetail | ||||
|     /// <summary> | ||||
|     /// 带命名规则的数据库列名 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<NamingConventions, string[]> _namingColumnNames = new(); | ||||
|     private readonly NonBlockingDictionary<NamingConventions, string[]> _namingColumnNames = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取数据库列名 | ||||
|   | ||||
| @@ -65,7 +65,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory | ||||
|     /// <summary> | ||||
|     /// 作业计划集合 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<string, Scheduler> _schedulers = new(); | ||||
|     private readonly NonBlockingDictionary<string, Scheduler> _schedulers = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 作业计划构建器集合 | ||||
|   | ||||
| @@ -369,11 +369,13 @@ internal sealed class ScheduleHostedService : BackgroundService | ||||
|                             // 写入作业执行详细日志 | ||||
|                             if (executionException == null) | ||||
|                             { | ||||
|                                 jobLogger?.LogInformation("{jobExecutingContext}", jobExecutingContext); | ||||
|                                 if (jobLogger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information) == true) | ||||
|                                     jobLogger?.LogInformation("{jobExecutingContext}", jobExecutingContext); | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 jobLogger?.LogError(executionException, "{jobExecutingContext}", jobExecutingContext); | ||||
|                                 if (jobLogger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Error) == true) | ||||
|                                     jobLogger?.LogError(executionException, "{jobExecutingContext}", jobExecutingContext); | ||||
|                             } | ||||
|  | ||||
|                             // 记录作业触发器运行信息 | ||||
|   | ||||
| @@ -380,7 +380,7 @@ public partial class Trigger | ||||
|     /// <summary> | ||||
|     /// 带命名规则的数据库列名 | ||||
|     /// </summary> | ||||
|     private readonly ConcurrentDictionary<NamingConventions, string[]> _namingColumnNames = new(); | ||||
|     private readonly NonBlockingDictionary<NamingConventions, string[]> _namingColumnNames = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取数据库列名 | ||||
|   | ||||
| @@ -83,11 +83,11 @@ public static class SpecificationDocumentBuilder | ||||
|  | ||||
|         // 初始化常量 | ||||
|         _groupOrderRegex = new Regex(@"@(?<order>[0-9]+$)"); | ||||
|         GetActionGroupsCached = new ConcurrentDictionary<MethodInfo, IEnumerable<GroupExtraInfo>>(); | ||||
|         GetControllerGroupsCached = new ConcurrentDictionary<Type, IEnumerable<GroupExtraInfo>>(); | ||||
|         GetGroupOpenApiInfoCached = new ConcurrentDictionary<string, SpecificationOpenApiInfo>(); | ||||
|         GetControllerTagCached = new ConcurrentDictionary<ControllerActionDescriptor, string>(); | ||||
|         GetActionTagCached = new ConcurrentDictionary<ApiDescription, string>(); | ||||
|         GetActionGroupsCached = new NonBlockingDictionary<MethodInfo, IEnumerable<GroupExtraInfo>>(); | ||||
|         GetControllerGroupsCached = new NonBlockingDictionary<Type, IEnumerable<GroupExtraInfo>>(); | ||||
|         GetGroupOpenApiInfoCached = new NonBlockingDictionary<string, SpecificationOpenApiInfo>(); | ||||
|         GetControllerTagCached = new NonBlockingDictionary<ControllerActionDescriptor, string>(); | ||||
|         GetActionTagCached = new NonBlockingDictionary<ApiDescription, string>(); | ||||
|  | ||||
|         // 默认分组,支持多个逗号分割 | ||||
|         DocumentGroupExtras = new List<GroupExtraInfo> { ResolveGroupExtraInfo(_specificationDocumentSettings.DefaultGroupName) }; | ||||
| @@ -143,7 +143,7 @@ public static class SpecificationDocumentBuilder | ||||
|     /// <summary> | ||||
|     /// 获取分组信息缓存集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<string, SpecificationOpenApiInfo> GetGroupOpenApiInfoCached; | ||||
|     private static readonly NonBlockingDictionary<string, SpecificationOpenApiInfo> GetGroupOpenApiInfoCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取分组配置信息 | ||||
| @@ -738,7 +738,7 @@ public static class SpecificationDocumentBuilder | ||||
|     /// <summary> | ||||
|     /// 获取控制器组缓存集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<Type, IEnumerable<GroupExtraInfo>> GetControllerGroupsCached; | ||||
|     private static readonly NonBlockingDictionary<Type, IEnumerable<GroupExtraInfo>> GetControllerGroupsCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取控制器分组列表 | ||||
| @@ -773,7 +773,7 @@ public static class SpecificationDocumentBuilder | ||||
|     /// <summary> | ||||
|     /// <see cref="GetActionGroups(MethodInfo)"/> 缓存集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<MethodInfo, IEnumerable<GroupExtraInfo>> GetActionGroupsCached; | ||||
|     private static readonly NonBlockingDictionary<MethodInfo, IEnumerable<GroupExtraInfo>> GetActionGroupsCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取动作方法分组列表 | ||||
| @@ -808,7 +808,7 @@ public static class SpecificationDocumentBuilder | ||||
|     /// <summary> | ||||
|     /// <see cref="GetActionTag(ApiDescription)"/> 缓存集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<ControllerActionDescriptor, string> GetControllerTagCached; | ||||
|     private static readonly NonBlockingDictionary<ControllerActionDescriptor, string> GetControllerTagCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取控制器标签 | ||||
| @@ -835,7 +835,7 @@ public static class SpecificationDocumentBuilder | ||||
|     /// <summary> | ||||
|     /// <see cref="GetActionTag(ApiDescription)"/> 缓存集合 | ||||
|     /// </summary> | ||||
|     private static readonly ConcurrentDictionary<ApiDescription, string> GetActionTagCached; | ||||
|     private static readonly NonBlockingDictionary<ApiDescription, string> GetActionTagCached; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取动作方法标签 | ||||
|   | ||||
| @@ -51,12 +51,12 @@ public static class UnifyContext | ||||
|     /// <summary> | ||||
|     /// 规范化结果提供器 | ||||
|     /// </summary> | ||||
|     internal static ConcurrentDictionary<string, UnifyMetadata> UnifyProviders = new(); | ||||
|     internal static NonBlockingDictionary<string, UnifyMetadata> UnifyProviders = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 规范化序列化配置 | ||||
|     /// </summary> | ||||
|     internal static ConcurrentDictionary<string, object> UnifySerializerSettings = new(); | ||||
|     internal static NonBlockingDictionary<string, object> UnifySerializerSettings = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取异常元数据 | ||||
|   | ||||
| @@ -14,7 +14,7 @@ using System.Collections.Concurrent; | ||||
| namespace ThingsGateway.Extension; | ||||
|  | ||||
| /// <summary> | ||||
| ///     <see cref="ConcurrentDictionary{TKey, TValue}" /> 拓展类 | ||||
| ///     <see cref="NonBlockingDictionary{TKey, TValue}" /> 拓展类 | ||||
| /// </summary> | ||||
| internal static class ConcurrentDictionaryExtensions | ||||
| { | ||||
| @@ -24,7 +24,7 @@ internal static class ConcurrentDictionaryExtensions | ||||
|     /// <typeparam name="TKey">字典键类型</typeparam> | ||||
|     /// <typeparam name="TValue">字典值类型</typeparam> | ||||
|     /// <param name="dictionary"> | ||||
|     ///     <see cref="ConcurrentDictionary{TKey, TValue}" /> | ||||
|     ///     <see cref="NonBlockingDictionary{TKey, TValue}" /> | ||||
|     /// </param> | ||||
|     /// <param name="key"> | ||||
|     ///     <typeparamref name="TKey" /> | ||||
| @@ -36,7 +36,7 @@ internal static class ConcurrentDictionaryExtensions | ||||
|     /// <returns> | ||||
|     ///     <see cref="bool" /> | ||||
|     /// </returns> | ||||
|     internal static bool TryUpdate<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary | ||||
|     internal static bool TryUpdate<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dictionary | ||||
|         , TKey key | ||||
|         , Func<TValue, TValue> updateFactory | ||||
|         , out TValue? value) | ||||
|   | ||||
| @@ -241,7 +241,7 @@ internal static class IDictionaryExtensions | ||||
|     /// </summary> | ||||
|     /// <remarks>其中键是由值通过给定的选择器函数生成的。</remarks> | ||||
|     /// <param name="dictionary"> | ||||
|     ///     <see cref="ConcurrentDictionary{TKey, TValue}" /> | ||||
|     ///     <see cref="NonBlockingDictionary{TKey, TValue}" /> | ||||
|     /// </param> | ||||
|     /// <param name="values"> | ||||
|     ///     <see cref="IEnumerable{T}" /> | ||||
| @@ -249,7 +249,7 @@ internal static class IDictionaryExtensions | ||||
|     /// <param name="keySelector">键选择器</param> | ||||
|     /// <typeparam name="TKey">字典键类型</typeparam> | ||||
|     /// <typeparam name="TValue">字典值类型</typeparam> | ||||
|     internal static void TryAdd<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary, | ||||
|     internal static void TryAdd<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dictionary, | ||||
|         IEnumerable<TValue>? values, Func<TValue, TKey> keySelector) | ||||
|         where TKey : notnull | ||||
|     { | ||||
|   | ||||
| @@ -21,20 +21,20 @@ internal sealed class CoreOptions | ||||
|     /// <summary> | ||||
|     ///     已注册的组件元数据集合 | ||||
|     /// </summary> | ||||
|     internal readonly ConcurrentDictionary<string, ComponentMetadata> _metadataOfRegistered; | ||||
|     internal readonly NonBlockingDictionary<string, ComponentMetadata> _metadataOfRegistered; | ||||
|  | ||||
|     /// <summary> | ||||
|     ///     子选项集合 | ||||
|     /// </summary> | ||||
|     internal readonly ConcurrentDictionary<Type, object> _optionsInstances; | ||||
|     internal readonly NonBlockingDictionary<Type, object> _optionsInstances; | ||||
|  | ||||
|     /// <summary> | ||||
|     ///     <inheritdoc cref="CoreOptions" /> | ||||
|     /// </summary> | ||||
|     internal CoreOptions() | ||||
|     { | ||||
|         _optionsInstances = new ConcurrentDictionary<Type, object>(); | ||||
|         _metadataOfRegistered = new ConcurrentDictionary<string, ComponentMetadata>(StringComparer.OrdinalIgnoreCase); | ||||
|         _optionsInstances = new NonBlockingDictionary<Type, object>(); | ||||
|         _metadataOfRegistered = new NonBlockingDictionary<string, ComponentMetadata>(StringComparer.OrdinalIgnoreCase); | ||||
|  | ||||
|         EntryComponentTypes = []; | ||||
|     } | ||||
|   | ||||
| @@ -32,7 +32,7 @@ public sealed class ObjectPropertyGetter<T> where T : class | ||||
|     /// <summary> | ||||
|     ///     对象类型实例属性值访问器集合 | ||||
|     /// </summary> | ||||
|     internal readonly ConcurrentDictionary<string, Func<object, object?>> _propertyGetters = new(); | ||||
|     internal readonly NonBlockingDictionary<string, Func<object, object?>> _propertyGetters = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     ///     <inheritdoc cref="ObjectPropertyGetter{T}" /> | ||||
|   | ||||
| @@ -32,7 +32,7 @@ public sealed class ObjectPropertySetter<T> where T : class | ||||
|     /// <summary> | ||||
|     ///     对象类型实例属性值设置器集合 | ||||
|     /// </summary> | ||||
|     internal readonly ConcurrentDictionary<string, Action<object, object?>> _propertySetters = new(); | ||||
|     internal readonly NonBlockingDictionary<string, Action<object, object?>> _propertySetters = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     ///     <inheritdoc cref="ObjectPropertySetter{T}" /> | ||||
|   | ||||
| @@ -27,7 +27,7 @@ public sealed class HttpDeclarativeBuilder | ||||
|     /// <summary> | ||||
|     ///     HTTP 声明式 <see cref="IHttpDeclarativeExtractor" /> 提取器集合 | ||||
|     /// </summary> | ||||
|     internal static readonly ConcurrentDictionary<Type, IHttpDeclarativeExtractor> _extractors = new([ | ||||
|     internal static readonly NonBlockingDictionary<Type, IHttpDeclarativeExtractor> _extractors = new([ | ||||
|         new(typeof(BaseAddressDeclarativeExtractor), new BaseAddressDeclarativeExtractor()), | ||||
|         new(typeof(ValidationDeclarativeExtractor), new ValidationDeclarativeExtractor()), | ||||
|         new(typeof(AutoSetHostHeaderDeclarativeExtractor), new AutoSetHostHeaderDeclarativeExtractor()), | ||||
| @@ -56,7 +56,7 @@ public sealed class HttpDeclarativeBuilder | ||||
|     ///     HTTP 声明式 <see cref="IHttpDeclarativeExtractor" /> 提取器集合(冻结) | ||||
|     /// </summary> | ||||
|     /// <remarks>该集合用于确保某些 HTTP 声明式提取器始终位于最后。</remarks> | ||||
|     internal static readonly ConcurrentDictionary<Type, IFrozenHttpDeclarativeExtractor> _frozenExtractors = new([ | ||||
|     internal static readonly NonBlockingDictionary<Type, IFrozenHttpDeclarativeExtractor> _frozenExtractors = new([ | ||||
|         new(typeof(MultipartDeclarativeExtractor), new MultipartDeclarativeExtractor()), | ||||
|         new(typeof(HttpMultipartFormDataBuilderDeclarativeExtractor), | ||||
|             new HttpMultipartFormDataBuilderDeclarativeExtractor()), | ||||
|   | ||||
| @@ -251,7 +251,8 @@ public sealed class ProfilerDelegatingHandler(ILogger<Logging> logger, IOptions< | ||||
|         // 检查是否配置(注册)了日志程序 | ||||
|         if (remoteOptions.IsLoggingRegistered) | ||||
|         { | ||||
|             logger.Log(remoteOptions.ProfilerLogLevel, "{message}", message); | ||||
|             if (logger?.IsEnabled(remoteOptions.ProfilerLogLevel) == true) | ||||
|                 logger.Log(remoteOptions.ProfilerLogLevel, "{message}", message); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|   | ||||
| @@ -27,7 +27,7 @@ public class MessagePackContentProcessor : HttpContentProcessorBase | ||||
|     /// <summary> | ||||
|     ///     MessagePack 序列化器委托字典缓存 | ||||
|     /// </summary> | ||||
|     internal static readonly ConcurrentDictionary<Type, Func<object, byte[]>> _serializerCache = new(); | ||||
|     internal static readonly NonBlockingDictionary<Type, Func<object, byte[]>> _serializerCache = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     ///     初始化 MessagePack 序列化器委托 | ||||
|   | ||||
| @@ -23,7 +23,7 @@ public class MemoryCache : Cache | ||||
| { | ||||
|     #region 属性 | ||||
|     /// <summary>缓存核心</summary> | ||||
|     protected ConcurrentDictionary<String, CacheItem> _cache = new(); | ||||
|     protected NonBlockingDictionary<String, CacheItem> _cache = new(); | ||||
|  | ||||
|     /// <summary>容量。容量超标时,采用LRU机制删除,默认100_000</summary> | ||||
|     public Int32 Capacity { get; set; } = 100_000; | ||||
| @@ -379,7 +379,7 @@ public class MemoryCache : Cache | ||||
|     /// <returns></returns> | ||||
|     public override IDictionary<String, T> GetDictionary<T>(String key) | ||||
|     { | ||||
|         var item = GetOrAddItem(key, k => new ConcurrentDictionary<String, T>()); | ||||
|         var item = GetOrAddItem(key, k => new NonBlockingDictionary<String, T>()); | ||||
|         return item.Visit<IDictionary<String, T>>() ?? | ||||
|          throw new InvalidCastException($"Unable to convert the value of [{key}] from {item.TypeCode} to {typeof(IDictionary<String, T>)}"); | ||||
|     } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| 后续例程与使用说明均以Redis为例,各缓存实现类似。   | ||||
|  | ||||
| ### 内存缓存 MemoryCache | ||||
| MemoryCache核心是并发字典ConcurrentDictionary,由于省去了序列化和网络通信,使得它具有千万级超高性能。   | ||||
| MemoryCache核心是并发字典NonBlockingDictionary,由于省去了序列化和网络通信,使得它具有千万级超高性能。   | ||||
| MemoryCache支持过期时间,默认容量10万个,未过期key超过该值后,每60秒根据LRU清理溢出部分。   | ||||
| 常用于进程内千万级以下数据缓存场景。   | ||||
|  | ||||
|   | ||||
| @@ -44,7 +44,7 @@ public static class CollectionHelper | ||||
|     { | ||||
|         //if (collection == null) return null; | ||||
|  | ||||
|         if (collection is ConcurrentDictionary<TKey, TValue> cdiv && cdiv.Keys is IList<TKey> list) return list; | ||||
|         if (collection is NonBlockingDictionary<TKey, TValue> cdiv && cdiv.Keys is IList<TKey> list) return list; | ||||
|  | ||||
|         if (collection.Count == 0) return []; | ||||
|         lock (collection) | ||||
| @@ -65,8 +65,8 @@ public static class CollectionHelper | ||||
|     { | ||||
|         //if (collection == null) return null; | ||||
|  | ||||
|         //if (collection is ConcurrentDictionary<TKey, TValue> cdiv) return cdiv.Values as IList<TValue>; | ||||
|         if (collection is ConcurrentDictionary<TKey, TValue> cdiv && cdiv.Values is IList<TValue> list) return list; | ||||
|         //if (collection is NonBlockingDictionary<TKey, TValue> cdiv) return cdiv.Values as IList<TValue>; | ||||
|         if (collection is NonBlockingDictionary<TKey, TValue> cdiv && cdiv.Values is IList<TValue> list) return list; | ||||
|  | ||||
|         if (collection.Count == 0) return []; | ||||
|         lock (collection) | ||||
|   | ||||
| @@ -9,7 +9,7 @@ namespace ThingsGateway.NewLife.Collections; | ||||
| /// </remarks> | ||||
| public class ConcurrentHashSet<T> : IEnumerable<T> where T : notnull | ||||
| { | ||||
|     private readonly ConcurrentDictionary<T, Byte> _dic = new(); | ||||
|     private readonly NonBlockingDictionary<T, Byte> _dic = new(); | ||||
|  | ||||
|     /// <summary>是否空集合</summary> | ||||
|     public Boolean IsEmpty => _dic.IsEmpty; | ||||
|   | ||||
| @@ -0,0 +1,77 @@ | ||||
| // Copyright (c) Vladimir Sadov. All rights reserved. | ||||
| // | ||||
| // This file is distributed under the MIT License. See LICENSE.md for details. | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
| { | ||||
|     internal abstract partial class DictionaryImpl<TKey, TKeyStore, TValue> | ||||
|         : DictionaryImpl<TKey, TValue> | ||||
|     { | ||||
|         internal override Snapshot GetSnapshot() | ||||
|         { | ||||
|             return new SnapshotImpl(this); | ||||
|         } | ||||
|  | ||||
|         private class SnapshotImpl : Snapshot | ||||
|         { | ||||
|             private readonly DictionaryImpl<TKey, TKeyStore, TValue> _table; | ||||
|  | ||||
|             public SnapshotImpl(DictionaryImpl<TKey, TKeyStore, TValue> dict) | ||||
|             { | ||||
|                 this._table = dict; | ||||
|  | ||||
|                 // linearization point. | ||||
|                 // if table is quiescent and has no copy in progress, | ||||
|                 // we can simply iterate over its table. | ||||
|                 while (_table._newTable != null) | ||||
|                 { | ||||
|                     // there is a copy in progress, finish it and try again | ||||
|                     _table.HelpCopy(copy_all: true); | ||||
|                     this._table = (DictionaryImpl<TKey, TKeyStore, TValue>)this._table._topDict._table; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             public override int Count => _table.Count; | ||||
|  | ||||
|             public override bool MoveNext() | ||||
|             { | ||||
|                 var entries = this._table._entries; | ||||
|                 while (_idx < entries.Length) | ||||
|                 { | ||||
|                     var nextEntry = entries[_idx++]; | ||||
|  | ||||
|                     if (nextEntry.value != null) | ||||
|                     { | ||||
|                         var nextKstore = nextEntry.key; | ||||
|                         if (nextKstore == null) | ||||
|                         { | ||||
|                             // slot was deleted. | ||||
|                             continue; | ||||
|                         } | ||||
|  | ||||
|                         _curKey = _table.keyFromEntry(nextKstore); | ||||
|                         object nextV = _table.TryGetValue(_curKey); | ||||
|                         if (nextV != null) | ||||
|                         { | ||||
|                             _curValue = _table.FromObjectValue(nextV); | ||||
|                             return true; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 _curKey = default; | ||||
|                 _curValue = default; | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             public override void Reset() | ||||
|             { | ||||
|                 _idx = 0; | ||||
|                 _curKey = default; | ||||
|                 _curValue = default; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,93 @@ | ||||
| // Copyright (c) Vladimir Sadov. All rights reserved. | ||||
| // | ||||
| // This file is distributed under the MIT License. See LICENSE.md for details. | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
| { | ||||
|     internal abstract class DictionaryImpl | ||||
|     { | ||||
|         internal enum ValueMatch | ||||
|         { | ||||
|             Any,            // sets new value unconditionally, used by index set and TryRemove(key) | ||||
|             NullOrDead,     // set value if original value is null or dead, used by Add/TryAdd | ||||
|             NotNullOrDead,  // set value if original value is alive, used by Remove | ||||
|             OldValue,       // sets new value if old value matches | ||||
|         } | ||||
|  | ||||
|         internal sealed class Prime | ||||
|         { | ||||
|             internal object originalValue; | ||||
|  | ||||
|             public Prime(object originalValue) | ||||
|             { | ||||
|                 this.originalValue = originalValue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         internal static readonly object TOMBSTONE = new object(); | ||||
|         internal static readonly Prime TOMBPRIME = new Prime(TOMBSTONE); | ||||
|         internal static readonly object NULLVALUE = new object(); | ||||
|  | ||||
|         // represents a trivially copied empty entry | ||||
|         // we insert it in the old table during rehashing | ||||
|         // to reduce chances that more entries are added | ||||
|         protected const int TOMBPRIMEHASH = 1 << 31; | ||||
|  | ||||
|         // we cannot distigush zero keys from uninitialized state | ||||
|         // so we force them to have this special hash instead | ||||
|         protected const int ZEROHASH = 1 << 30; | ||||
|  | ||||
|         // all regular hashes have both these bits set | ||||
|         // to be different from either 0, TOMBPRIMEHASH or ZEROHASH | ||||
|         // having only these bits set in a case of Ref key means that the slot is permanently deleted. | ||||
|         protected const int SPECIAL_HASH_BITS = TOMBPRIMEHASH | ZEROHASH; | ||||
|  | ||||
|         // Heuristic to decide if we have reprobed toooo many times.  Running over | ||||
|         // the reprobe limit on a 'get' call acts as a 'miss'; on a 'put' call it | ||||
|         // can trigger a table resize.  Several places must have exact agreement on | ||||
|         // what the reprobe_limit is, so we share it here. | ||||
|         protected const int REPROBE_LIMIT = 4; | ||||
|         protected const int REPROBE_LIMIT_SHIFT = 8; | ||||
|  | ||||
|         protected static int ReprobeLimit(int lenMask) | ||||
|         { | ||||
|             // 1/2 of table with some extra | ||||
|             return REPROBE_LIMIT + (lenMask >> REPROBE_LIMIT_SHIFT); | ||||
|         } | ||||
|  | ||||
|         protected static bool EntryValueNullOrDead(object entryValue) | ||||
|         { | ||||
|             return entryValue == null || entryValue == TOMBSTONE; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         protected static int ReduceHashToIndex(int fullHash, int lenMask) | ||||
|         { | ||||
|             var h = (uint)fullHash; | ||||
|  | ||||
|             // xor-shift some upper bits down, in case if variations are mostly in high bits | ||||
|             // and scatter the bits a little to break up clusters if hashes are periodic (like 42, 43, 44, ...) | ||||
|             // long clusters can cause long reprobes. small clusters are ok though. | ||||
|             h ^= h >> 15; | ||||
|             h ^= h >> 8; | ||||
|             h += (h >> 3) * 2654435769u; | ||||
|  | ||||
|             return (int)h & lenMask; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal static object ToObjectValue<TValue>(TValue value) | ||||
|         { | ||||
|             if (default(TValue) != null) | ||||
|             { | ||||
|                 return new Boxed<TValue>(value); | ||||
|             } | ||||
|  | ||||
|             return (object)value ?? NULLVALUE; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,138 @@ | ||||
| // Copyright (c) Vladimir Sadov. All rights reserved. | ||||
| // | ||||
| // This file is distributed under the MIT License. See LICENSE.md for details. | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
| { | ||||
|     internal sealed class DictionaryImplBoxed<TKey, TValue> | ||||
|             : DictionaryImpl<TKey, Boxed<TKey>, TValue> | ||||
|     { | ||||
|         internal DictionaryImplBoxed(int capacity, NonBlockingDictionary<TKey, TValue> topDict) | ||||
|             : base(capacity, topDict) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         internal DictionaryImplBoxed(int capacity, DictionaryImplBoxed<TKey, TValue> other) | ||||
|             : base(capacity, other) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForPut(ref Boxed<TKey> entryKey, TKey key) | ||||
|         { | ||||
|             var entryKeyValue = entryKey; | ||||
|             if (entryKeyValue == null) | ||||
|             { | ||||
|                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, new Boxed<TKey>(key), null); | ||||
|                 if (entryKeyValue == null) | ||||
|                 { | ||||
|                     // claimed a new slot | ||||
|                     this.allocatedSlotCount.Increment(); | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return _keyComparer.Equals(key, entryKeyValue.Value); | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForCopy(ref Boxed<TKey> entryKey, Boxed<TKey> key) | ||||
|         { | ||||
|             var entryKeyValue = entryKey; | ||||
|             if (entryKeyValue == null) | ||||
|             { | ||||
|                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, null); | ||||
|                 if (entryKeyValue == null) | ||||
|                 { | ||||
|                     // claimed a new slot | ||||
|                     this.allocatedSlotCount.Increment(); | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return _keyComparer.Equals(key.Value, entryKeyValue.Value); | ||||
|         } | ||||
|  | ||||
|         protected override bool keyEqual(TKey key, Boxed<TKey> entryKey) | ||||
|         { | ||||
|             //NOTE: slots are claimed in two stages - claim a hash, then set a key | ||||
|             //      it is possible to observe a slot with a null key, but with hash already set | ||||
|             //      that is not a match since the key is not yet in the table | ||||
|             if (entryKey == null) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             return _keyComparer.Equals(key, entryKey.Value); | ||||
|         } | ||||
|  | ||||
|         protected override DictionaryImpl<TKey, Boxed<TKey>, TValue> CreateNew(int capacity) | ||||
|         { | ||||
|             return new DictionaryImplBoxed<TKey, TValue>(capacity, this); | ||||
|         } | ||||
|  | ||||
|         protected override TKey keyFromEntry(Boxed<TKey> entryKey) | ||||
|         { | ||||
|             return entryKey.Value; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| #pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() | ||||
|     internal class Boxed<T> | ||||
|     { | ||||
|         // 0 - allow writes, 1 - someone is writing, 2 frozen. | ||||
|         public int writeStatus; | ||||
|         public T Value; | ||||
|  | ||||
|         public Boxed(T key) | ||||
|         { | ||||
|             this.Value = key; | ||||
|         } | ||||
|  | ||||
|         public override bool Equals(object obj) | ||||
|         { | ||||
|             return EqualityComparer<T>.Default.Equals(this.Value, Unsafe.As<Boxed<T>>(obj).Value); | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TryVolatileWrite(T value) | ||||
|         { | ||||
|             if (Interlocked.CompareExchange(ref writeStatus, 1, 0) == 0) | ||||
|             { | ||||
|                 Value = value; | ||||
|                 Volatile.Write(ref writeStatus, 0); | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TryCompareExchange(T oldValue, T newValue, out bool changed) | ||||
|         { | ||||
|             changed = false; | ||||
|             if (Interlocked.CompareExchange(ref writeStatus, 1, 0) != 0) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if (EqualityComparer<T>.Default.Equals(Value, oldValue)) | ||||
|             { | ||||
|                 Value = newValue; | ||||
|                 changed = true; | ||||
|             } | ||||
|  | ||||
|             Volatile.Write(ref writeStatus, 0); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal void Freeze() | ||||
|         { | ||||
|             // Wait for writers (1) to leave. Already 2 is ok, or set 0 -> 2. | ||||
|             while (Interlocked.CompareExchange(ref writeStatus, 2, 0) == 1) ; | ||||
|         } | ||||
|     } | ||||
| #pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() | ||||
| } | ||||
| @@ -0,0 +1,143 @@ | ||||
| // Copyright (c) Vladimir Sadov. All rights reserved. | ||||
| // | ||||
| // This file is distributed under the MIT License. See LICENSE.md for details. | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
| { | ||||
|     internal sealed class DictionaryImplInt<TValue> | ||||
|                 : DictionaryImpl<int, int, TValue> | ||||
|     { | ||||
|         internal DictionaryImplInt(int capacity, NonBlockingDictionary<int, TValue> topDict) | ||||
|             : base(capacity, topDict) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         internal DictionaryImplInt(int capacity, DictionaryImplInt<TValue> other) | ||||
|             : base(capacity, other) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForPut(ref int entryKey, int key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForCopy(ref int entryKey, int key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         private bool TryClaimSlot(ref int entryKey, int key) | ||||
|         { | ||||
|             var entryKeyValue = entryKey; | ||||
|             //zero keys are claimed via hash | ||||
|             if (entryKeyValue == 0 & key != 0) | ||||
|             { | ||||
|                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, 0); | ||||
|                 if (entryKeyValue == 0) | ||||
|                 { | ||||
|                     // claimed a new slot | ||||
|                     this.allocatedSlotCount.Increment(); | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return key == entryKeyValue || _keyComparer.Equals(key, entryKeyValue); | ||||
|         } | ||||
|  | ||||
|         protected override int hash(int key) | ||||
|         { | ||||
|             if (key == 0) | ||||
|             { | ||||
|                 return ZEROHASH; | ||||
|             } | ||||
|  | ||||
|             return base.hash(key); | ||||
|         } | ||||
|  | ||||
|         protected override bool keyEqual(int key, int entryKey) | ||||
|         { | ||||
|             return key == entryKey || _keyComparer.Equals(key, entryKey); | ||||
|         } | ||||
|  | ||||
|         protected override DictionaryImpl<int, int, TValue> CreateNew(int capacity) | ||||
|         { | ||||
|             return new DictionaryImplInt<TValue>(capacity, this); | ||||
|         } | ||||
|  | ||||
|         protected override int keyFromEntry(int entryKey) | ||||
|         { | ||||
|             return entryKey; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal sealed class DictionaryImplIntNoComparer<TValue> | ||||
|             : DictionaryImpl<int, int, TValue> | ||||
|     { | ||||
|         internal DictionaryImplIntNoComparer(int capacity, NonBlockingDictionary<int, TValue> topDict) | ||||
|             : base(capacity, topDict) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         internal DictionaryImplIntNoComparer(int capacity, DictionaryImplIntNoComparer<TValue> other) | ||||
|             : base(capacity, other) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForPut(ref int entryKey, int key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForCopy(ref int entryKey, int key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         private bool TryClaimSlot(ref int entryKey, int key) | ||||
|         { | ||||
|             var entryKeyValue = entryKey; | ||||
|             //zero keys are claimed via hash | ||||
|             if (entryKeyValue == 0 & key != 0) | ||||
|             { | ||||
|                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, 0); | ||||
|                 if (entryKeyValue == 0) | ||||
|                 { | ||||
|                     // claimed a new slot | ||||
|                     this.allocatedSlotCount.Increment(); | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return key == entryKeyValue; | ||||
|         } | ||||
|  | ||||
|         // inline the base implementation to devirtualize calls to hash and keyEqual | ||||
|         internal override object TryGetValue(int key) | ||||
|         { | ||||
|             return base.TryGetValue(key); | ||||
|         } | ||||
|  | ||||
|         protected override int hash(int key) | ||||
|         { | ||||
|             return (key == 0) ? | ||||
|                 ZEROHASH : | ||||
|                 key | SPECIAL_HASH_BITS; | ||||
|         } | ||||
|  | ||||
|         protected override bool keyEqual(int key, int entryKey) | ||||
|         { | ||||
|             return key == entryKey; | ||||
|         } | ||||
|  | ||||
|         protected override DictionaryImpl<int, int, TValue> CreateNew(int capacity) | ||||
|         { | ||||
|             return new DictionaryImplIntNoComparer<TValue>(capacity, this); | ||||
|         } | ||||
|  | ||||
|         protected override int keyFromEntry(int entryKey) | ||||
|         { | ||||
|             return entryKey; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,143 @@ | ||||
| // Copyright (c) Vladimir Sadov. All rights reserved. | ||||
| // | ||||
| // This file is distributed under the MIT License. See LICENSE.md for details. | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
| { | ||||
|     internal sealed class DictionaryImplLong<TValue> | ||||
|                 : DictionaryImpl<long, long, TValue> | ||||
|     { | ||||
|         internal DictionaryImplLong(int capacity, NonBlockingDictionary<long, TValue> topDict) | ||||
|             : base(capacity, topDict) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         internal DictionaryImplLong(int capacity, DictionaryImplLong<TValue> other) | ||||
|             : base(capacity, other) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForPut(ref long entryKey, long key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForCopy(ref long entryKey, long key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         private bool TryClaimSlot(ref long entryKey, long key) | ||||
|         { | ||||
|             var entryKeyValue = entryKey; | ||||
|             //zero keys are claimed via hash | ||||
|             if (entryKeyValue == 0 && key != 0) | ||||
|             { | ||||
|                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, 0); | ||||
|                 if (entryKeyValue == 0) | ||||
|                 { | ||||
|                     // claimed a new slot | ||||
|                     this.allocatedSlotCount.Increment(); | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return key == entryKeyValue || _keyComparer.Equals(key, entryKeyValue); | ||||
|         } | ||||
|  | ||||
|         protected override int hash(long key) | ||||
|         { | ||||
|             if (key == 0) | ||||
|             { | ||||
|                 return ZEROHASH; | ||||
|             } | ||||
|  | ||||
|             return base.hash(key); | ||||
|         } | ||||
|  | ||||
|         protected override bool keyEqual(long key, long entryKey) | ||||
|         { | ||||
|             return key == entryKey || _keyComparer.Equals(key, entryKey); | ||||
|         } | ||||
|  | ||||
|         protected override DictionaryImpl<long, long, TValue> CreateNew(int capacity) | ||||
|         { | ||||
|             return new DictionaryImplLong<TValue>(capacity, this); | ||||
|         } | ||||
|  | ||||
|         protected override long keyFromEntry(long entryKey) | ||||
|         { | ||||
|             return entryKey; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal sealed class DictionaryImplLongNoComparer<TValue> | ||||
|             : DictionaryImpl<long, long, TValue> | ||||
|     { | ||||
|         internal DictionaryImplLongNoComparer(int capacity, NonBlockingDictionary<long, TValue> topDict) | ||||
|             : base(capacity, topDict) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         internal DictionaryImplLongNoComparer(int capacity, DictionaryImplLongNoComparer<TValue> other) | ||||
|             : base(capacity, other) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForPut(ref long entryKey, long key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForCopy(ref long entryKey, long key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         private bool TryClaimSlot(ref long entryKey, long key) | ||||
|         { | ||||
|             var entryKeyValue = entryKey; | ||||
|             //zero keys are claimed via hash | ||||
|             if (entryKeyValue == 0 && key != 0) | ||||
|             { | ||||
|                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, 0); | ||||
|                 if (entryKeyValue == 0) | ||||
|                 { | ||||
|                     // claimed a new slot | ||||
|                     this.allocatedSlotCount.Increment(); | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return key == entryKeyValue; | ||||
|         } | ||||
|  | ||||
|         // inline the base implementation to devirtualize calls to hash and keyEqual | ||||
|         internal override object TryGetValue(long key) | ||||
|         { | ||||
|             return base.TryGetValue(key); | ||||
|         } | ||||
|  | ||||
|         protected override int hash(long key) | ||||
|         { | ||||
|             return (key == 0) ? | ||||
|                 ZEROHASH : | ||||
|                 key.GetHashCode() | SPECIAL_HASH_BITS; | ||||
|         } | ||||
|  | ||||
|         protected override bool keyEqual(long key, long entryKey) | ||||
|         { | ||||
|             return key == entryKey; | ||||
|         } | ||||
|  | ||||
|         protected override DictionaryImpl<long, long, TValue> CreateNew(int capacity) | ||||
|         { | ||||
|             return new DictionaryImplLongNoComparer<TValue>(capacity, this); | ||||
|         } | ||||
|  | ||||
|         protected override long keyFromEntry(long entryKey) | ||||
|         { | ||||
|             return entryKey; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,143 @@ | ||||
| // Copyright (c) Vladimir Sadov. All rights reserved. | ||||
| // | ||||
| // This file is distributed under the MIT License. See LICENSE.md for details. | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
| { | ||||
|     internal sealed class DictionaryImplNint<TValue> | ||||
|                 : DictionaryImpl<nint, nint, TValue> | ||||
|     { | ||||
|         internal DictionaryImplNint(int capacity, NonBlockingDictionary<nint, TValue> topDict) | ||||
|             : base(capacity, topDict) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         internal DictionaryImplNint(int capacity, DictionaryImplNint<TValue> other) | ||||
|             : base(capacity, other) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForPut(ref nint entryKey, nint key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForCopy(ref nint entryKey, nint key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         private bool TryClaimSlot(ref nint entryKey, nint key) | ||||
|         { | ||||
|             var entryKeyValue = entryKey; | ||||
|             //zero keys are claimed via hash | ||||
|             if (entryKeyValue == 0 && key != 0) | ||||
|             { | ||||
|                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, (nint)0); | ||||
|                 if (entryKeyValue == 0) | ||||
|                 { | ||||
|                     // claimed a new slot | ||||
|                     this.allocatedSlotCount.Increment(); | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return key == entryKeyValue || _keyComparer.Equals(key, entryKeyValue); | ||||
|         } | ||||
|  | ||||
|         protected override int hash(nint key) | ||||
|         { | ||||
|             if (key == 0) | ||||
|             { | ||||
|                 return ZEROHASH; | ||||
|             } | ||||
|  | ||||
|             return base.hash(key); | ||||
|         } | ||||
|  | ||||
|         protected override bool keyEqual(nint key, nint entryKey) | ||||
|         { | ||||
|             return key == entryKey || _keyComparer.Equals(key, entryKey); | ||||
|         } | ||||
|  | ||||
|         protected override DictionaryImpl<nint, nint, TValue> CreateNew(int capacity) | ||||
|         { | ||||
|             return new DictionaryImplNint<TValue>(capacity, this); | ||||
|         } | ||||
|  | ||||
|         protected override nint keyFromEntry(nint entryKey) | ||||
|         { | ||||
|             return entryKey; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal sealed class DictionaryImplNintNoComparer<TValue> | ||||
|             : DictionaryImpl<nint, nint, TValue> | ||||
|     { | ||||
|         internal DictionaryImplNintNoComparer(int capacity, NonBlockingDictionary<nint, TValue> topDict) | ||||
|             : base(capacity, topDict) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         internal DictionaryImplNintNoComparer(int capacity, DictionaryImplNintNoComparer<TValue> other) | ||||
|             : base(capacity, other) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForPut(ref nint entryKey, nint key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForCopy(ref nint entryKey, nint key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         private bool TryClaimSlot(ref nint entryKey, nint key) | ||||
|         { | ||||
|             var entryKeyValue = entryKey; | ||||
|             //zero keys are claimed via hash | ||||
|             if (entryKeyValue == 0 && key != 0) | ||||
|             { | ||||
|                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, (nint)0); | ||||
|                 if (entryKeyValue == 0) | ||||
|                 { | ||||
|                     // claimed a new slot | ||||
|                     this.allocatedSlotCount.Increment(); | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return key == entryKeyValue; | ||||
|         } | ||||
|  | ||||
|         // inline the base implementation to devirtualize calls to hash and keyEqual | ||||
|         internal override object TryGetValue(nint key) | ||||
|         { | ||||
|             return base.TryGetValue(key); | ||||
|         } | ||||
|  | ||||
|         protected override int hash(nint key) | ||||
|         { | ||||
|             return (key == 0) ? | ||||
|                 ZEROHASH : | ||||
|                 key.GetHashCode() | SPECIAL_HASH_BITS; | ||||
|         } | ||||
|  | ||||
|         protected override bool keyEqual(nint key, nint entryKey) | ||||
|         { | ||||
|             return key == entryKey; | ||||
|         } | ||||
|  | ||||
|         protected override DictionaryImpl<nint, nint, TValue> CreateNew(int capacity) | ||||
|         { | ||||
|             return new DictionaryImplNintNoComparer<TValue>(capacity, this); | ||||
|         } | ||||
|  | ||||
|         protected override nint keyFromEntry(nint entryKey) | ||||
|         { | ||||
|             return entryKey; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,95 @@ | ||||
| // Copyright (c) Vladimir Sadov. All rights reserved. | ||||
| // | ||||
| // This file is distributed under the MIT License. See LICENSE.md for details. | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| using System.Diagnostics; | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
| { | ||||
|     internal sealed class DictionaryImplRef<TKey, TKeyStore, TValue> | ||||
|             : DictionaryImpl<TKey, TKey, TValue> | ||||
|     { | ||||
|         internal DictionaryImplRef(int capacity, NonBlockingDictionary<TKey, TValue> topDict) | ||||
|             : base(capacity, topDict) | ||||
|         { | ||||
|             Debug.Assert(!typeof(TKey).IsValueType); | ||||
|         } | ||||
|  | ||||
|         internal DictionaryImplRef(int capacity, DictionaryImplRef<TKey, TKeyStore, TValue> other) | ||||
|             : base(capacity, other) | ||||
|         { | ||||
|             Debug.Assert(!typeof(TKey).IsValueType); | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForPut(ref TKey entryKey, TKey key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         protected override bool TryClaimSlotForCopy(ref TKey entryKey, TKey key) | ||||
|         { | ||||
|             return TryClaimSlot(ref entryKey, key); | ||||
|         } | ||||
|  | ||||
|         private bool TryClaimSlot(ref TKey entryKey, TKey key) | ||||
|         { | ||||
|             ref object keyLocation = ref Unsafe.As<TKey, object>(ref entryKey); | ||||
|             object entryKeyValue = keyLocation; | ||||
|             if (entryKeyValue == null) | ||||
|             { | ||||
|                 entryKeyValue = Interlocked.CompareExchange(ref keyLocation, key, null); | ||||
|                 if (entryKeyValue == null) | ||||
|                 { | ||||
|                     // claimed a new slot | ||||
|                     this.allocatedSlotCount.Increment(); | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return (object)key == entryKeyValue || | ||||
|                 _keyComparer.Equals(key, Unsafe.As<object, TKey>(ref entryKeyValue)); | ||||
|         } | ||||
|  | ||||
|         // inline the base implementation to devirtualize calls to hash and keyEqual | ||||
|         internal override object TryGetValue(TKey key) | ||||
|         { | ||||
|             return base.TryGetValue(key); | ||||
|         } | ||||
|  | ||||
|         protected override int hash(TKey key) | ||||
|         { | ||||
|             return base.hash(key); | ||||
|         } | ||||
|  | ||||
|         protected override bool keyEqual(TKey key, TKey entryKey) | ||||
|         { | ||||
|             if ((object)key == (object)entryKey) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             //NOTE: slots are claimed in two stages - claim a hash, then set a key | ||||
|             //      it is possible to observe a slot with a null key, but with hash already set | ||||
|             //      that is not a match since the key is not yet in the table | ||||
|             if (entryKey == null) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             return _keyComparer.Equals(entryKey, key); | ||||
|         } | ||||
|  | ||||
|         protected override DictionaryImpl<TKey, TKey, TValue> CreateNew(int capacity) | ||||
|         { | ||||
|             return new DictionaryImplRef<TKey, TKeyStore, TValue>(capacity, this); | ||||
|         } | ||||
|  | ||||
|         protected override TKey keyFromEntry(TKey entryKey) | ||||
|         { | ||||
|             return entryKey; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,80 @@ | ||||
| // Copyright (c) Vladimir Sadov. All rights reserved. | ||||
| // | ||||
| // This file is distributed under the MIT License. See LICENSE.md for details. | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
| { | ||||
|     internal abstract class DictionaryImpl<TKey, TValue> | ||||
|         : DictionaryImpl | ||||
|     { | ||||
|         private readonly bool _valueIsValueType = typeof(TValue).IsValueType; | ||||
|  | ||||
|         internal IEqualityComparer<TKey> _keyComparer; | ||||
|  | ||||
|         internal abstract void Clear(); | ||||
|         internal abstract int Count { get; } | ||||
|  | ||||
|         internal abstract object TryGetValue(TKey key); | ||||
|         internal abstract bool PutIfMatch(TKey key, TValue newVal, ref TValue oldValue, ValueMatch match); | ||||
|         internal abstract bool RemoveIfMatch(TKey key, ref TValue oldValue, ValueMatch match); | ||||
|         internal abstract TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory); | ||||
|  | ||||
|         internal abstract Snapshot GetSnapshot(); | ||||
|  | ||||
|         internal abstract class Snapshot | ||||
|         { | ||||
|             protected int _idx; | ||||
|             protected TKey _curKey; | ||||
|             protected TValue _curValue; | ||||
|  | ||||
|             public abstract int Count { get; } | ||||
|             public abstract bool MoveNext(); | ||||
|             public abstract void Reset(); | ||||
|  | ||||
|             internal DictionaryEntry Entry | ||||
|             { | ||||
|                 get | ||||
|                 { | ||||
|                     return new DictionaryEntry(_curKey, _curValue); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             internal KeyValuePair<TKey, TValue> Current | ||||
|             { | ||||
|                 get | ||||
|                 { | ||||
|                     return new KeyValuePair<TKey, TValue>(this._curKey, _curValue); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         protected TValue FromObjectValue(object obj) | ||||
|         { | ||||
|             // regular value type | ||||
|             if (default(TValue) != null) | ||||
|             { | ||||
|                 return Unsafe.As<Boxed<TValue>>(obj).Value; | ||||
|             } | ||||
|  | ||||
|             // null | ||||
|             if (obj == NULLVALUE) | ||||
|             { | ||||
|                 return default(TValue); | ||||
|             } | ||||
|  | ||||
|             // ref type | ||||
|             if (!_valueIsValueType) | ||||
|             { | ||||
|                 return Unsafe.As<object, TValue>(ref obj); | ||||
|             } | ||||
|  | ||||
|             // nullable | ||||
|             return (TValue)obj; | ||||
|         } | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -0,0 +1,204 @@ | ||||
| // Copyright (c) Vladimir Sadov. All rights reserved. | ||||
| // | ||||
| // This file is distributed under the MIT License. See LICENSE.md for details. | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Runtime.InteropServices; | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Scalable 32bit counter that can be used from multiple threads. | ||||
|     /// </summary> | ||||
|     public sealed class Counter32 : CounterBase | ||||
|     { | ||||
|         private class Cell | ||||
|         { | ||||
|             [StructLayout(LayoutKind.Explicit, Size = CACHE_LINE * 2 - OBJ_HEADER_SIZE)] | ||||
|             public struct SpacedCounter | ||||
|             { | ||||
|                 [FieldOffset(CACHE_LINE - OBJ_HEADER_SIZE)] | ||||
|                 public int count; | ||||
|             } | ||||
|  | ||||
|             public SpacedCounter counter; | ||||
|         } | ||||
|  | ||||
|         // spaced out counters | ||||
|         private Cell[]? cells; | ||||
|  | ||||
|         // default counter | ||||
|         private int count; | ||||
|  | ||||
|         // delayed estimated count | ||||
|         private int lastCount; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Returns the value of the counter at the time of the call. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// The value may miss in-progress updates if the counter is being concurrently modified. | ||||
|         /// </remarks> | ||||
|         public int Value | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 var count = this.count; | ||||
|                 var cells = this.cells; | ||||
|  | ||||
|                 if (cells != null) | ||||
|                 { | ||||
|                     for (int i = 0; i < cells.Length; i++) | ||||
|                     { | ||||
|                         var cell = cells[i]; | ||||
|                         if (cell != null) | ||||
|                         { | ||||
|                             count += cell.counter.count; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 return count; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Returns the approximate value of the counter at the time of the call. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// EstimatedValue could be significantly cheaper to obtain, but may be slightly delayed. | ||||
|         /// </remarks> | ||||
|         public int EstimatedValue | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (this.cells == null) | ||||
|                 { | ||||
|                     return this.count; | ||||
|                 } | ||||
|  | ||||
|                 var curTicks = (uint)Environment.TickCount; | ||||
|                 // more than a millisecond passed? | ||||
|                 if (curTicks != lastCountTicks) | ||||
|                 { | ||||
|                     lastCountTicks = curTicks; | ||||
|                     lastCount = Value; | ||||
|                 } | ||||
|  | ||||
|                 return lastCount; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Increments the counter by 1. | ||||
|         /// </summary> | ||||
|         public void Increment() | ||||
|         { | ||||
|             int curCellCount = this.cellCount; | ||||
|             var drift = increment(ref GetCountRef(curCellCount)); | ||||
|  | ||||
|             if (drift != 0) | ||||
|             { | ||||
|                 TryAddCell(curCellCount); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Decrements the counter by 1. | ||||
|         /// </summary> | ||||
|         public void Decrement() | ||||
|         { | ||||
|             int curCellCount = this.cellCount; | ||||
|             var drift = decrement(ref GetCountRef(curCellCount)); | ||||
|  | ||||
|             if (drift != 0) | ||||
|             { | ||||
|                 TryAddCell(curCellCount); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Increments the counter by 'value'. | ||||
|         /// </summary> | ||||
|         public void Add(int value) | ||||
|         { | ||||
|             int curCellCount = this.cellCount; | ||||
|             var drift = add(ref GetCountRef(curCellCount), value); | ||||
|  | ||||
|             if (drift != 0) | ||||
|             { | ||||
|                 TryAddCell(curCellCount); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private ref int GetCountRef(int curCellCount) | ||||
|         { | ||||
|             ref var countRef = ref count; | ||||
|  | ||||
|             Cell[]? cells; | ||||
|             if ((cells = this.cells) != null && curCellCount > 1) | ||||
|             { | ||||
|                 var cell = cells[GetIndex((uint)curCellCount)]; | ||||
|                 if (cell != null) | ||||
|                 { | ||||
|                     countRef = ref cell.counter.count; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return ref countRef; | ||||
|         } | ||||
|  | ||||
|         private static int increment(ref int val) | ||||
|         { | ||||
|             return -val - 1 + Interlocked.Increment(ref val); | ||||
|         } | ||||
|  | ||||
|         private static int add(ref int val, int inc) | ||||
|         { | ||||
|             return -val - inc + Interlocked.Add(ref val, inc); | ||||
|         } | ||||
|  | ||||
|         private static int decrement(ref int val) | ||||
|         { | ||||
|             return val - 1 - Interlocked.Decrement(ref val); | ||||
|         } | ||||
|  | ||||
|         private void TryAddCell(int curCellCount) | ||||
|         { | ||||
|             if (curCellCount < s_MaxCellCount) | ||||
|             { | ||||
|                 TryAddCellCore(curCellCount); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.NoInlining)] | ||||
|         private void TryAddCellCore(int curCellCount) | ||||
|         { | ||||
|             var cells = this.cells; | ||||
|             if (cells == null) | ||||
|             { | ||||
|                 var newCells = new Cell[s_MaxCellCount]; | ||||
|                 cells = Interlocked.CompareExchange(ref this.cells, newCells, null) ?? newCells; | ||||
|             } | ||||
|  | ||||
|             if (cells[curCellCount] == null) | ||||
|             { | ||||
|                 Interlocked.CompareExchange(ref cells[curCellCount], new Cell(), null); | ||||
|             } | ||||
|  | ||||
|             if (this.cellCount == curCellCount) | ||||
|             { | ||||
|                 Interlocked.CompareExchange(ref this.cellCount, curCellCount + 1, curCellCount); | ||||
|                 //if (Interlocked.CompareExchange(ref this.cellCount, curCellCount + 1, curCellCount) == curCellCount) | ||||
|                 //{ | ||||
|                 //    System.Console.WriteLine(curCellCount + 1); | ||||
|                 //} | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,203 @@ | ||||
| // Copyright (c) Vladimir Sadov. All rights reserved. | ||||
| // | ||||
| // This file is distributed under the MIT License. See LICENSE.md for details. | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Runtime.InteropServices; | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Scalable 64bit counter that can be used from multiple threads. | ||||
|     /// </summary> | ||||
|     public sealed class Counter64 : CounterBase | ||||
|     { | ||||
|         private class Cell | ||||
|         { | ||||
|             [StructLayout(LayoutKind.Explicit, Size = CACHE_LINE * 2 - OBJ_HEADER_SIZE)] | ||||
|             public struct SpacedCounter | ||||
|             { | ||||
|                 [FieldOffset(CACHE_LINE - OBJ_HEADER_SIZE)] | ||||
|                 public long count; | ||||
|             } | ||||
|  | ||||
|             public SpacedCounter counter; | ||||
|         } | ||||
|  | ||||
|         // spaced out counters | ||||
|         private Cell[]? cells; | ||||
|  | ||||
|         // default counter | ||||
|         private long count; | ||||
|  | ||||
|         // delayed count | ||||
|         private long lastCount; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Returns the value of the counter at the time of the call. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// The value may miss in-progress updates if the counter is being concurrently modified. | ||||
|         /// </remarks> | ||||
|         public long Value | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 var count = this.count; | ||||
|                 var cells = this.cells; | ||||
|  | ||||
|                 if (cells != null) | ||||
|                 { | ||||
|                     for (int i = 0; i < cells.Length; i++) | ||||
|                     { | ||||
|                         var cell = cells[i]; | ||||
|                         if (cell != null) | ||||
|                         { | ||||
|                             count += cell.counter.count; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 return count; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Returns the approximate value of the counter at the time of the call. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// EstimatedValue could be significantly cheaper to obtain, but may be slightly delayed. | ||||
|         /// </remarks> | ||||
|         public long EstimatedValue | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (this.cellCount == 0) | ||||
|                 { | ||||
|                     return Value; | ||||
|                 } | ||||
|  | ||||
|                 var curTicks = (uint)Environment.TickCount; | ||||
|                 // more than a millisecond passed? | ||||
|                 if (curTicks != lastCountTicks) | ||||
|                 { | ||||
|                     lastCountTicks = curTicks; | ||||
|                     lastCount = Value; | ||||
|                 } | ||||
|  | ||||
|                 return lastCount; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Increments the counter by 1. | ||||
|         /// </summary> | ||||
|         public void Increment() | ||||
|         { | ||||
|             int curCellCount = this.cellCount; | ||||
|             var drift = increment(ref GetCountRef(curCellCount)); | ||||
|  | ||||
|             if (drift != 0) | ||||
|             { | ||||
|                 TryAddCell(curCellCount); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Decrements the counter by 1. | ||||
|         /// </summary> | ||||
|         public void Decrement() | ||||
|         { | ||||
|             int curCellCount = this.cellCount; | ||||
|             var drift = decrement(ref GetCountRef(curCellCount)); | ||||
|  | ||||
|             if (drift != 0) | ||||
|             { | ||||
|                 TryAddCell(curCellCount); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Increments the counter by 'value'. | ||||
|         /// </summary> | ||||
|         public void Add(int value) | ||||
|         { | ||||
|             int curCellCount = this.cellCount; | ||||
|             var drift = add(ref GetCountRef(curCellCount), value); | ||||
|  | ||||
|             if (drift != 0) | ||||
|             { | ||||
|                 TryAddCell(curCellCount); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private ref long GetCountRef(int curCellCount) | ||||
|         { | ||||
|             ref var countRef = ref count; | ||||
|  | ||||
|             Cell[]? cells; | ||||
|             if ((cells = this.cells) != null && curCellCount > 1) | ||||
|             { | ||||
|                 var cell = cells[GetIndex((uint)curCellCount)]; | ||||
|                 if (cell != null) | ||||
|                 { | ||||
|                     countRef = ref cell.counter.count; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return ref countRef; | ||||
|         } | ||||
|  | ||||
|         private static long increment(ref long val) | ||||
|         { | ||||
|             return -val - 1 + Interlocked.Increment(ref val); | ||||
|         } | ||||
|  | ||||
|         private static long add(ref long val, int inc) | ||||
|         { | ||||
|             return -val - inc + Interlocked.Add(ref val, inc); | ||||
|         } | ||||
|  | ||||
|         private static long decrement(ref long val) | ||||
|         { | ||||
|             return val - 1 - Interlocked.Decrement(ref val); | ||||
|         } | ||||
|  | ||||
|         private void TryAddCell(int curCellCount) | ||||
|         { | ||||
|             if (curCellCount < s_MaxCellCount) | ||||
|             { | ||||
|                 TryAddCellCore(curCellCount); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void TryAddCellCore(int curCellCount) | ||||
|         { | ||||
|             var cells = this.cells; | ||||
|             if (cells == null) | ||||
|             { | ||||
|                 var newCells = new Cell[s_MaxCellCount]; | ||||
|                 cells = Interlocked.CompareExchange(ref this.cells, newCells, null) ?? newCells; | ||||
|             } | ||||
|  | ||||
|             if (cells[curCellCount] == null) | ||||
|             { | ||||
|                 Interlocked.CompareExchange(ref cells[curCellCount], new Cell(), null); | ||||
|             } | ||||
|  | ||||
|             if (this.cellCount == curCellCount) | ||||
|             { | ||||
|                 Interlocked.CompareExchange(ref this.cellCount, curCellCount + 1, curCellCount); | ||||
|                 //if (Interlocked.CompareExchange(ref this.cellCount, curCellCount + 1, curCellCount) == curCellCount) | ||||
|                 //{ | ||||
|                 //    System.Console.WriteLine(curCellCount + 1); | ||||
|                 //} | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,38 @@ | ||||
| // Copyright (c) Vladimir Sadov. All rights reserved. | ||||
| // | ||||
| // This file is distributed under the MIT License. See LICENSE.md for details. | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Scalable counter base. | ||||
|     /// </summary> | ||||
|     public class CounterBase | ||||
|     { | ||||
|         private protected const int CACHE_LINE = 64; | ||||
|         private protected const int OBJ_HEADER_SIZE = 8; | ||||
|  | ||||
|         private protected static readonly int s_MaxCellCount = Util.AlignToPowerOfTwo(Environment.ProcessorCount) + 1; | ||||
|  | ||||
|         // how many cells we have | ||||
|         private protected int cellCount; | ||||
|  | ||||
|         // delayed count time | ||||
|         private protected uint lastCountTicks; | ||||
|  | ||||
|         private protected CounterBase() | ||||
|         { | ||||
|             // touch static | ||||
|             _ = s_MaxCellCount; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private protected unsafe static int GetIndex(uint cellCount) | ||||
|         { | ||||
|             nuint addr = (nuint)(&cellCount); | ||||
|             return (int)(addr % cellCount); | ||||
|         } | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -0,0 +1,21 @@ | ||||
| using System.Diagnostics; | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
| { | ||||
|     internal static class Util | ||||
|     { | ||||
|         // returns 2^x >= size | ||||
|         internal static int AlignToPowerOfTwo(int size) | ||||
|         { | ||||
|             Debug.Assert(size > 0); | ||||
|  | ||||
|             size--; | ||||
|             size |= size >> 1; | ||||
|             size |= size >> 2; | ||||
|             size |= size >> 4; | ||||
|             size |= size >> 8; | ||||
|             size |= size >> 16; | ||||
|             return size + 1; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -31,8 +31,8 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull | ||||
|     /// <summary>最小个数。默认1</summary> | ||||
|     public Int32 Min { get; set; } = 1; | ||||
|  | ||||
|     /// <summary>空闲清理时间。最小个数之上的资源超过空闲时间时被清理,默认10s</summary> | ||||
|     public Int32 IdleTime { get; set; } = 10; | ||||
|     /// <summary>空闲清理时间。最小个数之上的资源超过空闲时间时被清理,默认60s</summary> | ||||
|     public Int32 IdleTime { get; set; } = 60; | ||||
|  | ||||
|     /// <summary>完全空闲清理时间。最小个数之下的资源超过空闲时间时被清理,默认0s永不清理</summary> | ||||
|     public Int32 AllIdleTime { get; set; } = 0; | ||||
| @@ -44,7 +44,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull | ||||
|     private readonly ConcurrentQueue<Item> _free2 = new(); | ||||
|  | ||||
|     /// <summary>借出去的放在这</summary> | ||||
|     private readonly ConcurrentDictionary<T, Item> _busy = new(); | ||||
|     private readonly NonBlockingDictionary<T, Item> _busy = new(); | ||||
|  | ||||
|     //private readonly Object SyncRoot = new(); | ||||
|     #endregion | ||||
| @@ -126,9 +126,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull | ||||
|                 if (Max > 0 && count >= Max) | ||||
|                 { | ||||
|                     var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}"; | ||||
|  | ||||
|                     WriteLog("Acquire Max " + msg); | ||||
|  | ||||
|                     throw new Exception(Name + " " + msg); | ||||
|                 } | ||||
|  | ||||
| @@ -268,7 +266,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull | ||||
|         { | ||||
|             if (_timer != null) return; | ||||
|  | ||||
|             _timer = new TimerX(Work, null, 5000, 5000) { Async = true }; | ||||
|             _timer = new TimerX(Work, null, 60000, 60000) { Async = true }; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -281,7 +279,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull | ||||
|         var count = 0; | ||||
|  | ||||
|         // 清理过期不还。避免有借没还 | ||||
|         if (!_busy.IsEmpty) | ||||
|         if (AllIdleTime > 0 && !_busy.IsEmpty) | ||||
|         { | ||||
|             var exp = TimerX.Now.AddSeconds(-AllIdleTime); | ||||
|             foreach (var item in _busy) | ||||
|   | ||||
							
								
								
									
										241
									
								
								src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,241 @@ | ||||
| using ThingsGateway.NewLife.Log; | ||||
| using ThingsGateway.NewLife.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.NewLife.Collections; | ||||
|  | ||||
| /// <summary>资源池。支持空闲释放,主要用于数据库连接池和网络连接池</summary> | ||||
| /// <remarks> | ||||
| /// 文档 https://newlifex.com/core/object_pool | ||||
| /// </remarks> | ||||
| /// <typeparam name="T"></typeparam> | ||||
| public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class | ||||
| { | ||||
|     #region 属性 | ||||
|     /// <summary>名称</summary> | ||||
|     public String Name { get; set; } | ||||
|  | ||||
|     private Int32 _FreeCount; | ||||
|     /// <summary>空闲个数</summary> | ||||
|     public Int32 FreeCount => _FreeCount; | ||||
|  | ||||
|     private Int32 _BusyCount; | ||||
|     /// <summary>繁忙个数</summary> | ||||
|     public Int32 BusyCount => _BusyCount; | ||||
|  | ||||
|     /// <summary>最大个数。默认0,0表示无上限</summary> | ||||
|     public Int32 Max { get; set; } = 0; | ||||
|  | ||||
|     private readonly object _syncRoot = new(); | ||||
|  | ||||
|     /// <summary>基础空闲集合。只保存最小个数,最热部分</summary> | ||||
|     private readonly Stack<T> _free = new(); | ||||
|  | ||||
|     /// <summary>借出去的放在这</summary> | ||||
|     private readonly HashSet<T> _busy = new(); | ||||
|  | ||||
|     //private readonly Object SyncRoot = new(); | ||||
|     #endregion | ||||
|  | ||||
|     #region 构造 | ||||
|     /// <summary>实例化一个资源池</summary> | ||||
|     public ObjectPoolLock() | ||||
|     { | ||||
|         var str = GetType().Name; | ||||
|         if (str.Contains('`')) str = str.Substring(null, "`"); | ||||
|         if (str != "Pool") | ||||
|             Name = str; | ||||
|         else | ||||
|             Name = $"Pool<{typeof(T).Name}>"; | ||||
|  | ||||
|     } | ||||
|     ~ObjectPoolLock() | ||||
|     { | ||||
|         this.TryDispose(); | ||||
|     } | ||||
|     /// <summary>销毁</summary> | ||||
|     /// <param name="disposing"></param> | ||||
|     protected override void Dispose(Boolean disposing) | ||||
|     { | ||||
|         base.Dispose(disposing); | ||||
|  | ||||
|         WriteLog($"Dispose {typeof(T).FullName} FreeCount={FreeCount:n0} BusyCount={BusyCount:n0}"); | ||||
|  | ||||
|         Clear(); | ||||
|     } | ||||
|  | ||||
|     private volatile Boolean _inited; | ||||
|     private void Init() | ||||
|     { | ||||
|         if (_inited) return; | ||||
|  | ||||
|         lock (lockThis) | ||||
|         { | ||||
|             if (_inited) return; | ||||
|             _inited = true; | ||||
|  | ||||
|             WriteLog($"Init {typeof(T).FullName} Max={Max}"); | ||||
|         } | ||||
|     } | ||||
|     #endregion | ||||
|  | ||||
|     #region 主方法 | ||||
|     /// <summary>借出</summary> | ||||
|     /// <returns></returns> | ||||
|     public virtual T Get() | ||||
|     { | ||||
|         T? pi = null; | ||||
|         do | ||||
|         { | ||||
|             lock (_syncRoot) | ||||
|             { | ||||
|                 if (_free.Count > 0) | ||||
|                 { | ||||
|                     pi = _free.Pop(); | ||||
|                     _FreeCount--; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     if (Max > 0 && BusyCount >= Max) | ||||
|                     { | ||||
|                         var msg = $"申请失败,已有 {BusyCount:n0} 达到或超过最大值 {Max:n0}"; | ||||
|                         WriteLog("Acquire Max " + msg); | ||||
|                         throw new Exception(Name + " " + msg); | ||||
|                     } | ||||
|  | ||||
|                     pi = OnCreate(); | ||||
|                     if (BusyCount == 0) Init(); | ||||
|  | ||||
| #if DEBUG | ||||
|                     WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, BusyCount + 1); | ||||
| #endif | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // 如果拿到的对象不可用,则重新借 | ||||
|         } while (pi == null || !OnGet(pi)); | ||||
|  | ||||
|         lock (_syncRoot) | ||||
|         { | ||||
|             // 加入繁忙集合 | ||||
|             _busy.Add(pi); | ||||
|  | ||||
|             _BusyCount++; | ||||
|         } | ||||
|         return pi; | ||||
|     } | ||||
|  | ||||
|     /// <summary>借出时是否可用</summary> | ||||
|     /// <param name="value"></param> | ||||
|     /// <returns></returns> | ||||
|     protected virtual Boolean OnGet(T value) => true; | ||||
|  | ||||
|     /// <summary>申请资源包装项,Dispose时自动归还到池中</summary> | ||||
|     /// <returns></returns> | ||||
|     public PoolItem<T> GetItem() => new(this, Get()); | ||||
|  | ||||
|     /// <summary>归还</summary> | ||||
|     /// <param name="value"></param> | ||||
|     public virtual Boolean Return(T value) | ||||
|     { | ||||
|         if (value == null) return false; | ||||
|         lock (_syncRoot) | ||||
|         { | ||||
|             // 从繁忙队列找到并移除缓存项 | ||||
|             if (!_busy.Remove(value)) | ||||
|             { | ||||
| #if DEBUG | ||||
|                 WriteLog("Return Error"); | ||||
| #endif | ||||
|  | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             _BusyCount--; | ||||
|         } | ||||
|  | ||||
|         // 是否可用 | ||||
|         if (!OnReturn(value)) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         if (value is DisposeBase db && db.Disposed) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         lock (_syncRoot) | ||||
|         { | ||||
|             _free.Push(value); | ||||
|             _FreeCount++; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /// <summary>归还时是否可用</summary> | ||||
|     /// <param name="value"></param> | ||||
|     /// <returns></returns> | ||||
|     protected virtual Boolean OnReturn(T value) => true; | ||||
|  | ||||
|     /// <summary>清空已有对象</summary> | ||||
|     public virtual Int32 Clear() | ||||
|     { | ||||
|         var count = _FreeCount + _BusyCount; | ||||
|  | ||||
|         //_busy.Clear(); | ||||
|         //_BusyCount = 0; | ||||
|  | ||||
|         //_free.Clear(); | ||||
|         //while (_free2.TryDequeue(out var rs)) ; | ||||
|         //_FreeCount = 0; | ||||
|  | ||||
|         lock (_syncRoot) | ||||
|         { | ||||
|             count = _FreeCount + _BusyCount; | ||||
|  | ||||
|             while (_free.Count > 0) | ||||
|             { | ||||
|                 var pi = _free.Pop(); | ||||
|                 OnDispose(pi); | ||||
|             } | ||||
|  | ||||
|             _FreeCount = 0; | ||||
|  | ||||
|             foreach (var item in _busy) | ||||
|             { | ||||
|                 OnDispose(item); | ||||
|             } | ||||
|             _busy.Clear(); | ||||
|             _BusyCount = 0; | ||||
|         } | ||||
|  | ||||
|         return count; | ||||
|     } | ||||
|  | ||||
|     /// <summary>销毁</summary> | ||||
|     /// <param name="value"></param> | ||||
|     protected virtual void OnDispose(T? value) => value.TryDispose(); | ||||
|     #endregion | ||||
|  | ||||
|     #region 重载 | ||||
|     /// <summary>创建实例</summary> | ||||
|     /// <returns></returns> | ||||
|     protected virtual T? OnCreate() => (T?)typeof(T).CreateInstance(); | ||||
|     #endregion | ||||
|     protected object lockThis = new(); | ||||
|  | ||||
|     #region 日志 | ||||
|     /// <summary>日志</summary> | ||||
|     public ILog Log { get; set; } = Logger.Null; | ||||
|  | ||||
|     /// <summary>写日志</summary> | ||||
|     /// <param name="format"></param> | ||||
|     /// <param name="args"></param> | ||||
|     public void WriteLog(String format, params Object?[] args) | ||||
|     { | ||||
|         if (Log?.Enable != true) return; | ||||
|  | ||||
|         Log.Info(Name + "." + format, args); | ||||
|     } | ||||
|     #endregion | ||||
| } | ||||
| @@ -0,0 +1,48 @@ | ||||
| namespace ThingsGateway.NewLife.Collections; | ||||
|  | ||||
|  | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
|  | ||||
| internal struct ValueStopwatch | ||||
| { | ||||
| #if !NET7_0_OR_GREATER | ||||
|     private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; | ||||
| #endif | ||||
|  | ||||
|     private readonly long _startTimestamp; | ||||
|  | ||||
|     public bool IsActive => _startTimestamp != 0; | ||||
|  | ||||
|     private ValueStopwatch(long startTimestamp) | ||||
|     { | ||||
|         _startTimestamp = startTimestamp; | ||||
|     } | ||||
|  | ||||
|     public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp()); | ||||
|  | ||||
|     public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) | ||||
|     { | ||||
| #if !NET7_0_OR_GREATER | ||||
|         var timestampDelta = endingTimestamp - startingTimestamp; | ||||
|         var ticks = (long)(TimestampToTicks * timestampDelta); | ||||
|         return new TimeSpan(ticks); | ||||
| #else | ||||
|         return Stopwatch.GetElapsedTime(startingTimestamp, endingTimestamp); | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     public TimeSpan GetElapsedTime() | ||||
|     { | ||||
|         // Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0. | ||||
|         // So it being 0 is a clear indication of default(ValueStopwatch) | ||||
|         if (!IsActive) | ||||
|         { | ||||
|             throw new InvalidOperationException("An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time."); | ||||
|         } | ||||
|  | ||||
|         var end = Stopwatch.GetTimestamp(); | ||||
|  | ||||
|         return GetElapsedTime(_startTimestamp, end); | ||||
|     } | ||||
| } | ||||
| @@ -5,30 +5,103 @@ namespace ThingsGateway.NewLife; | ||||
|  | ||||
| public class ExpiringDictionary<TKey, TValue> : IDisposable | ||||
| { | ||||
|     private ConcurrentDictionary<TKey, TValue> _dict = new(); | ||||
|     private readonly TimerX _cleanupTimer; | ||||
|  | ||||
|     public ExpiringDictionary(int cleanupInterval = 60000) | ||||
|     /// <summary>缓存项</summary> | ||||
|     public class CacheItem | ||||
|     { | ||||
|         _cleanupTimer = new TimerX(Clear, null, cleanupInterval, cleanupInterval) { Async = true }; | ||||
|         private TValue? _value; | ||||
|         /// <summary>数值</summary> | ||||
|         public TValue? Value { get => _value; } | ||||
|  | ||||
|         /// <summary>过期时间。系统启动以来的毫秒数</summary> | ||||
|         public Int64 ExpiredTime { get; set; } | ||||
|  | ||||
|         /// <summary>是否过期</summary> | ||||
|         public Boolean Expired => ExpiredTime <= Runtime.TickCount64; | ||||
|  | ||||
|         /// <summary>访问时间</summary> | ||||
|         public Int64 VisitTime { get; private set; } | ||||
|  | ||||
|         /// <summary>构造缓存项</summary> | ||||
|         /// <param name="value"></param> | ||||
|         /// <param name="expire"></param> | ||||
|         public CacheItem(TValue? value, Int32 expire) => Set(value, expire); | ||||
|  | ||||
|         /// <summary>设置数值和过期时间</summary> | ||||
|         /// <param name="value"></param> | ||||
|         /// <param name="expire">过期时间,秒</param> | ||||
|         public void Set(TValue value, Int32 expire) | ||||
|         { | ||||
|             _value = value; | ||||
|  | ||||
|             var now = VisitTime = Runtime.TickCount64; | ||||
|             if (expire <= 0) | ||||
|                 ExpiredTime = Int64.MaxValue; | ||||
|             else | ||||
|                 ExpiredTime = now + expire * 1000L; | ||||
|         } | ||||
|  | ||||
|         /// <summary>更新访问时间并返回数值</summary> | ||||
|         /// <returns></returns> | ||||
|         public TValue? Visit() | ||||
|         { | ||||
|             VisitTime = Runtime.TickCount64; | ||||
|             var rs = _value; | ||||
|             if (rs == null) return default; | ||||
|  | ||||
|             return rs; | ||||
|         } | ||||
|     } | ||||
|     private NonBlockingDictionary<TKey, CacheItem> _dict; | ||||
|  | ||||
|     private readonly TimerX _cleanupTimer; | ||||
|     private int defaultExpire = 60; | ||||
|     IEqualityComparer<TKey>? comparer; | ||||
|     public ExpiringDictionary(int expire = 60, IEqualityComparer<TKey>? comparer = null) | ||||
|     { | ||||
|         defaultExpire = expire; | ||||
|         this.comparer = comparer; | ||||
|         _dict = new NonBlockingDictionary<TKey, CacheItem>(comparer); | ||||
|  | ||||
|         _cleanupTimer = new TimerX(TimerClear, null, 60000, 60000) { Async = true }; | ||||
|     } | ||||
|  | ||||
|     public void TryAdd(TKey key, TValue value) | ||||
|     public bool TryAdd(TKey key, TValue value) | ||||
|     { | ||||
|         _dict.TryAdd(key, value); | ||||
|         if (_dict.TryGetValue(key, out var item)) | ||||
|         { | ||||
|             if (!item.Expired) return false; | ||||
|             item.Set(value, defaultExpire); | ||||
|             return true; | ||||
|         } | ||||
|         return _dict.TryAdd(key, new CacheItem(value, defaultExpire)); | ||||
|     } | ||||
|  | ||||
|     public bool TryGetValue(TKey key, out TValue value) | ||||
|     { | ||||
|         return _dict.TryGetValue(key, out value); | ||||
|         value = default; | ||||
|  | ||||
|         // 没有值,直接结束 | ||||
|         if (!_dict.TryGetValue(key, out var item) || item == null) return false; | ||||
|  | ||||
|         // 得到已有值 | ||||
|         value = item.Visit(); | ||||
|  | ||||
|         // 是否未过期的有效值 | ||||
|         return !item.Expired; | ||||
|     } | ||||
|     public TValue GetOrAdd(TKey key, Func<TKey, TValue> func) | ||||
|     { | ||||
|         return _dict.GetOrAdd(key, func); | ||||
|     } | ||||
|     public TValue GetOrAdd(TKey key, TValue value) | ||||
|     { | ||||
|         return _dict.GetOrAdd(key, value); | ||||
|         CacheItem? item = null; | ||||
|         do | ||||
|         { | ||||
|             if (_dict.TryGetValue(key, out item) && item != null) return item.Visit(); | ||||
|  | ||||
|             item ??= new CacheItem(func(key), defaultExpire); | ||||
|         } | ||||
|         while (!_dict.TryAdd(key, item)); | ||||
|  | ||||
|         return item.Visit(); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public bool TryRemove(TKey key) => _dict.TryRemove(key, out _); | ||||
| @@ -38,13 +111,38 @@ public class ExpiringDictionary<TKey, TValue> : IDisposable | ||||
|     private void Clear(object? state) | ||||
|     { | ||||
|         var data = _dict; | ||||
|         _dict = new(); | ||||
|         _dict = new(comparer); | ||||
|         data.Clear(); | ||||
|     } | ||||
|     private void TimerClear(object? state) | ||||
|     { | ||||
|  | ||||
|         var dic = _dict; | ||||
|         if (dic.IsEmpty) return; | ||||
|  | ||||
|         // 60分钟之内过期的数据,进入LRU淘汰 | ||||
|         var now = Runtime.TickCount64; | ||||
|  | ||||
|         // 这里先计算,性能很重要 | ||||
|         var toDels = new List<TKey>(); | ||||
|         foreach (var item in dic) | ||||
|         { | ||||
|             // 已过期,准备删除 | ||||
|             var ci = item.Value; | ||||
|             if (ci.ExpiredTime <= now) | ||||
|                 toDels.Add(item.Key); | ||||
|         } | ||||
|  | ||||
|         // 确认删除 | ||||
|         foreach (var item in toDels) | ||||
|         { | ||||
|             _dict.Remove(item); | ||||
|         } | ||||
|     } | ||||
|     public void Dispose() | ||||
|     { | ||||
|         _dict.Clear(); | ||||
|         _cleanupTimer.Dispose(); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -13,8 +13,8 @@ public class FastMapperOption | ||||
| public static class FastMapper | ||||
| { | ||||
|     // 泛型 + 非泛型共用缓存 | ||||
|     private static readonly ConcurrentDictionary<(Type Source, Type Target), Delegate> _mapCache | ||||
|         = new ConcurrentDictionary<(Type, Type), Delegate>(); | ||||
|     private static readonly NonBlockingDictionary<(Type Source, Type Target), Delegate> _mapCache | ||||
|         = new NonBlockingDictionary<(Type, Type), Delegate>(); | ||||
|  | ||||
|     #region 泛型入口 | ||||
|     public static TTarget Mapper<TSource, TTarget>(TSource source, FastMapperOption option = null) | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Foundation; | ||||
| namespace ThingsGateway.NewLife; | ||||
| 
 | ||||
| public class LinkedCancellationTokenSourceCache : IDisposable | ||||
| { | ||||
| @@ -63,6 +63,7 @@ public class LinkedCancellationTokenSourceCache : IDisposable | ||||
|             _cachedCts?.Dispose(); | ||||
|             _cachedCts = null!; | ||||
|         } | ||||
|         GC.SuppressFinalize(this); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @@ -14,10 +14,16 @@ using ThingsGateway.NewLife.Reflection; | ||||
| using ThingsGateway.NewLife.Serialization; | ||||
| using ThingsGateway.NewLife.Windows; | ||||
|  | ||||
| using System.Diagnostics; | ||||
| using System.Runtime; | ||||
|  | ||||
|  | ||||
| #if NETFRAMEWORK | ||||
| using System.Management; | ||||
|  | ||||
| using Microsoft.VisualBasic.Devices; | ||||
|  | ||||
|  | ||||
| #endif | ||||
| #if NETFRAMEWORK || NET6_0_OR_GREATER | ||||
| using Microsoft.Win32; | ||||
| @@ -42,7 +48,7 @@ public interface IMachineInfo | ||||
| ///  | ||||
| /// 刷新信息成本较高,建议采用单例模式 | ||||
| /// </remarks> | ||||
| public class MachineInfo : IExtend | ||||
| public class MachineInfo | ||||
| { | ||||
|     #region 属性 | ||||
|     /// <summary>系统名称</summary> | ||||
| @@ -88,11 +94,11 @@ public class MachineInfo : IExtend | ||||
|     [DisplayName("磁盘序列号")] | ||||
|     public String? DiskID { get; set; } | ||||
|  | ||||
|     /// <summary>内存总量。单位KB</summary> | ||||
|     /// <summary>内存总量。单位MB</summary> | ||||
|     [DisplayName("内存总量")] | ||||
|     public UInt64 Memory { get; set; } | ||||
|  | ||||
|     /// <summary>可用内存。单位KB</summary> | ||||
|     /// <summary>可用内存。单位MB</summary> | ||||
|     [DisplayName("可用内存")] | ||||
|     public UInt64 AvailableMemory { get; set; } | ||||
|  | ||||
| @@ -116,13 +122,98 @@ public class MachineInfo : IExtend | ||||
|     [DisplayName("电池剩余")] | ||||
|     public Double Battery { get; set; } | ||||
|  | ||||
|     private readonly Dictionary<String, Object?> _items = []; | ||||
|     IDictionary<String, Object?> IExtend.Items => _items; | ||||
| #if NET6_0_OR_GREATER | ||||
|     #region GC与进程内存信息 | ||||
|  | ||||
|     /// <summary>GC 认为“内存吃紧”的阈值。单位:MB</summary> | ||||
|     [DisplayName("GC高内存阈值")] | ||||
|     public UInt64 HighMemoryLoadThreshold { get; set; } | ||||
|  | ||||
|     /// <summary>GC 可用内存上限。单位:MB</summary> | ||||
|     [DisplayName("GC可用内存上限")] | ||||
|     public UInt64 TotalAvailableMemory { get; set; } | ||||
|  | ||||
|     /// <summary>当前托管堆容量。单位:MB</summary> | ||||
|     [DisplayName("托管堆容量")] | ||||
|     public UInt64 HeapSize { get; set; } | ||||
|  | ||||
|     /// <summary>托管堆已用内存。单位:MB</summary> | ||||
|     [DisplayName("托管堆已用")] | ||||
|     public UInt64 TotalMemory { get; set; } | ||||
|  | ||||
|     /// <summary>托管堆碎片大小。单位:MB</summary> | ||||
|     [DisplayName("托管堆碎片")] | ||||
|     public UInt64 FragmentedBytes { get; set; } | ||||
|  | ||||
|     /// <summary>GC识别可用内存。单位:MB</summary> | ||||
|     [DisplayName("GC识别可用内存")] | ||||
|     public UInt64 GCAvailableMemory { get; set; } | ||||
|  | ||||
|     /// <summary>GC 已提交的内存。单位:MB</summary> | ||||
|     [DisplayName("GC已提交内存")] | ||||
|     public UInt64 CommittedBytes { get; set; } | ||||
|  | ||||
|     /// <summary>GC 累计分配的托管内存。单位:MB</summary> | ||||
|     [DisplayName("GC累计分配")] | ||||
|     public UInt64 TotalAllocatedBytes { get; set; } | ||||
| #if NET8_0_OR_GREATER | ||||
|     /// <summary>GC 暂停累计时间。单位:毫秒</summary> | ||||
|     [DisplayName("GC累计暂停时间")] | ||||
|     public UInt64 TotalPauseDurationMs { get; set; } | ||||
| #endif | ||||
|     /// <summary>GC 代0收集次数</summary> | ||||
|     [DisplayName("GC Gen0 次数")] | ||||
|     public Int32 GcGen0Count { get; set; } | ||||
|  | ||||
|     /// <summary>GC 代1收集次数</summary> | ||||
|     [DisplayName("GC Gen1 次数")] | ||||
|     public Int32 GcGen1Count { get; set; } | ||||
|  | ||||
|     /// <summary>GC 代2收集次数</summary> | ||||
|     [DisplayName("GC Gen2 次数")] | ||||
|     public Int32 GcGen2Count { get; set; } | ||||
|  | ||||
|     /// <summary>Server GC 是否启用</summary> | ||||
|     [DisplayName("是否使用Server GC")] | ||||
|     public Boolean IsServerGC { get; set; } | ||||
|  | ||||
|     /// <summary>GC 延迟模式</summary> | ||||
|     [DisplayName("GC延迟模式")] | ||||
|     public GCLatencyMode? GCLatencyMode { get; set; } | ||||
|  | ||||
|     /// <summary>GC 固定对象数</summary> | ||||
|     [DisplayName("固定对象数")] | ||||
|     public Int64 PinnedObjectsCount { get; set; } | ||||
|  | ||||
|     /// <summary>终结队列挂起对象数</summary> | ||||
|     [DisplayName("终结挂起数")] | ||||
|     public Int64 FinalizationPendingCount { get; set; } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|     #region 进程内存信息 | ||||
|  | ||||
|     /// <summary>进程虚拟内存使用量。单位:MB</summary> | ||||
|     [DisplayName("虚拟内存")] | ||||
|     public UInt64 VirtualMemory { get; set; } | ||||
|  | ||||
|     /// <summary>进程私有内存使用量。单位:MB</summary> | ||||
|     [DisplayName("私有内存")] | ||||
|     public UInt64 PrivateMemory { get; set; } | ||||
|  | ||||
|     /// <summary>进程峰值工作集。单位:MB</summary> | ||||
|     [DisplayName("峰值工作集")] | ||||
|     public UInt64 PeakWorkingSet { get; set; } | ||||
|  | ||||
|     /// <summary>进程当前工作集。单位:MB</summary> | ||||
|     [DisplayName("当前工作集")] | ||||
|     public UInt64 WorkingSet { get; set; } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
| #endif | ||||
|  | ||||
|  | ||||
|     /// <summary>获取 或 设置 扩展属性数据</summary> | ||||
|     /// <param name="key"></param> | ||||
|     /// <returns></returns> | ||||
|     public Object? this[String key] { get => _items.TryGetValue(key, out var obj) ? obj : null; set => _items[key] = value; } | ||||
|     #endregion | ||||
|  | ||||
|     #region 全局静态 | ||||
| @@ -569,10 +660,50 @@ public class MachineInfo : IExtend | ||||
|         else if (Runtime.Linux) | ||||
|             RefreshLinux(); | ||||
|  | ||||
|         // 刷新 GC 与进程内存信息 | ||||
|         RefreshMemoryInfo(); | ||||
|  | ||||
|         RefreshSpeed(); | ||||
|  | ||||
|         Provider?.Refresh(this); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 刷新 GC 与进程内存相关信息 | ||||
|     /// </summary> | ||||
|     private void RefreshMemoryInfo() | ||||
|     { | ||||
| #if NET6_0_OR_GREATER | ||||
|         var info = GC.GetGCMemoryInfo(); | ||||
|         var proc = Process.GetCurrentProcess(); | ||||
|  | ||||
|         // GC 信息(单位:MB) | ||||
|         HighMemoryLoadThreshold = (ulong)(info.HighMemoryLoadThresholdBytes / 1024 / 1024); | ||||
|         TotalAvailableMemory = (ulong)(info.TotalAvailableMemoryBytes / 1024 / 1024); | ||||
|         HeapSize = (ulong)(info.HeapSizeBytes / 1024 / 1024); | ||||
|         TotalMemory = (ulong)(GC.GetTotalMemory(false) / 1024 / 1024); | ||||
|         FragmentedBytes = (ulong)(info.FragmentedBytes / 1024 / 1024); | ||||
|         GCAvailableMemory = (ulong)(info.TotalAvailableMemoryBytes - info.MemoryLoadBytes) / 1024 / 1024; | ||||
|         CommittedBytes = (ulong)(info.TotalCommittedBytes / 1024 / 1024); | ||||
|         TotalAllocatedBytes = (ulong)(GC.GetTotalAllocatedBytes(false) / 1024 / 1024); | ||||
| #if NET8_0_OR_GREATER | ||||
|         TotalPauseDurationMs = (ulong)GC.GetTotalPauseDuration().TotalMilliseconds; | ||||
| #endif | ||||
|         GcGen0Count = GC.CollectionCount(0); | ||||
|         GcGen1Count = GC.CollectionCount(1); | ||||
|         GcGen2Count = GC.CollectionCount(2); | ||||
|         IsServerGC = System.Runtime.GCSettings.IsServerGC; | ||||
|         GCLatencyMode = System.Runtime.GCSettings.LatencyMode; | ||||
|         PinnedObjectsCount = info.PinnedObjectsCount; | ||||
|         FinalizationPendingCount = info.FinalizationPendingCount; | ||||
|  | ||||
|         // 进程信息(单位:MB) | ||||
|         VirtualMemory = (ulong)(proc.VirtualMemorySize64 / 1024 / 1024); | ||||
|         PrivateMemory = (ulong)(proc.PrivateMemorySize64 / 1024 / 1024); | ||||
|         PeakWorkingSet = (ulong)(proc.PeakWorkingSet64 / 1024 / 1024); | ||||
|         WorkingSet = (ulong)(proc.WorkingSet64 / 1024 / 1024); | ||||
|  | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     private void RefreshWindows() | ||||
|     { | ||||
| @@ -580,8 +711,8 @@ public class MachineInfo : IExtend | ||||
|         ms.Init(); | ||||
|         if (GlobalMemoryStatusEx(ref ms)) | ||||
|         { | ||||
|             Memory = (ulong)(ms.ullTotalPhys / 1024.0); | ||||
|             AvailableMemory = (ulong)(ms.ullAvailPhys / 1024.0); | ||||
|             Memory = ms.ullTotalPhys / 1024 / 1024; | ||||
|             AvailableMemory = ms.ullAvailPhys / 1024 / 1024; | ||||
|         } | ||||
|  | ||||
|         GetSystemTimes(out var idleTime, out var kernelTime, out var userTime); | ||||
| @@ -675,28 +806,87 @@ public class MachineInfo : IExtend | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 🐳 容器内存使用 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     private static (ulong Total, ulong Used) GetCGroupMemoryUsage() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|  | ||||
|             string[] limitPaths = { | ||||
|             "/sys/fs/cgroup/memory/memory.limit_in_bytes", // cgroup v1 | ||||
|             "/sys/fs/cgroup/memory.max"                    // cgroup v2 | ||||
|         }; | ||||
|  | ||||
|             string[] usagePaths = { | ||||
|             "/sys/fs/cgroup/memory/memory.usage_in_bytes",  // cgroup v1 | ||||
|             "/sys/fs/cgroup/memory.current"                 // cgroup v2 | ||||
|         }; | ||||
|  | ||||
|             ulong total = ReadFirstAvailable(limitPaths); | ||||
|             ulong used = ReadFirstAvailable(usagePaths); | ||||
|  | ||||
|             // 容器无内存限制时 total 通常是 2^63-1,忽略 | ||||
|             if (total > (1UL << 60)) total = 0; | ||||
|  | ||||
|             return (total, used); | ||||
|  | ||||
|  | ||||
|         } | ||||
|         catch (Exception) | ||||
|         { | ||||
|             return (0, 0); | ||||
|         } | ||||
|         static ulong ReadFirstAvailable(string[] paths) | ||||
|         { | ||||
|             foreach (var path in paths) | ||||
|             { | ||||
|                 if (File.Exists(path)) | ||||
|                 { | ||||
|                     var content = File.ReadAllText(path).Trim(); | ||||
|                     if (content == "max") return ulong.MaxValue; | ||||
|                     if (ulong.TryParse(content, out var value)) return value; | ||||
|                 } | ||||
|             } | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private void RefreshLinux() | ||||
|     { | ||||
|         var dic = ReadInfo("/proc/meminfo"); | ||||
|         if (dic != null) | ||||
|         var (totalMemory, usedMemory) = GetCGroupMemoryUsage(); | ||||
|         if (totalMemory > 0 && usedMemory > 0 && totalMemory < ulong.MaxValue / 2) | ||||
|         { | ||||
|             if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty()) | ||||
|                 Memory = (UInt64)str.TrimEnd(" kB").ToLong(); | ||||
|  | ||||
|             ulong ma = 0; | ||||
|             if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty()) | ||||
|             Memory = totalMemory / 1024 / 1024; | ||||
|             AvailableMemory = (totalMemory - usedMemory) / 1024 / 1024; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             var dic = ReadInfo("/proc/meminfo"); | ||||
|             if (dic != null) | ||||
|             { | ||||
|                 ma = (UInt64)(str.TrimEnd(" kB").ToLong()); | ||||
|                 if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty()) | ||||
|                     Memory = (UInt64)str.TrimEnd(" kB").ToLong(); | ||||
|  | ||||
|                 ulong ma = 0; | ||||
|                 if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty()) | ||||
|                 { | ||||
|                     ma = (UInt64)(str.TrimEnd(" kB").ToLong() / 1024); | ||||
|                 } | ||||
|  | ||||
|                 //低于3.14内核的版本用 free+cache | ||||
|                 var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0); | ||||
|                 var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0); | ||||
|                 var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0); | ||||
|  | ||||
|                 var free = mf + mc + bf; | ||||
|  | ||||
|                 AvailableMemory = ma > free ? ma : free; | ||||
|             } | ||||
|  | ||||
|             //低于3.14内核的版本用 free+cache | ||||
|             var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||
|             var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||
|             var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||
|  | ||||
|             var free = mf + mc + bf; | ||||
|  | ||||
|             AvailableMemory = ma > free ? ma : free; | ||||
|         } | ||||
|  | ||||
|         // A2/A4温度获取,Buildroot,CPU温度和主板温度 | ||||
|   | ||||
| @@ -10,13 +10,18 @@ | ||||
| //  感谢您的下载和使用 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Foundation; | ||||
| namespace ThingsGateway.NewLife; | ||||
| 
 | ||||
| using System; | ||||
| using System.Threading; | ||||
| 
 | ||||
| public sealed class ReusableCancellationTokenSource : IDisposable | ||||
| { | ||||
|     ~ReusableCancellationTokenSource() | ||||
|     { | ||||
|         Dispose(); | ||||
|     } | ||||
| 
 | ||||
|     private readonly Timer _timer; | ||||
|     private CancellationTokenSource? _cts; | ||||
| 
 | ||||
| @@ -47,7 +52,7 @@ public sealed class ReusableCancellationTokenSource : IDisposable | ||||
|     /// <summary> | ||||
|     /// 获取一个 CTS,并启动超时 | ||||
|     /// </summary> | ||||
|     public CancellationTokenSource GetTokenSource(long timeout, CancellationToken external1 = default, CancellationToken external2 = default, CancellationToken external3 = default) | ||||
|     public CancellationToken GetTokenSource(long timeout, CancellationToken external1 = default, CancellationToken external2 = default, CancellationToken external3 = default) | ||||
|     { | ||||
|         TimeoutStatus = false; | ||||
| 
 | ||||
| @@ -57,7 +62,7 @@ public sealed class ReusableCancellationTokenSource : IDisposable | ||||
|         // 启动 Timer | ||||
|         _timer.Change(timeout, Timeout.Infinite); | ||||
| 
 | ||||
|         return _cts; | ||||
|         return _cts.Token; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| @@ -71,15 +76,16 @@ public sealed class ReusableCancellationTokenSource : IDisposable | ||||
|     /// </summary> | ||||
|     public void Cancel() | ||||
|     { | ||||
|         _cts?.SafeCancel(); | ||||
|         try { _cts?.Cancel(); } catch { } | ||||
|     } | ||||
| 
 | ||||
|     public void Dispose() | ||||
|     { | ||||
|         _cts?.SafeCancel(); | ||||
|         _cts?.SafeDispose(); | ||||
|         _linkedCtsCache.SafeDispose(); | ||||
|         _timer.SafeDispose(); | ||||
|         try { _cts?.Cancel(); } catch { } | ||||
|         try { _cts?.Dispose(); } catch { } | ||||
|         try { _linkedCtsCache?.Dispose(); } catch { } | ||||
|         try { _timer?.Dispose(); } catch { } | ||||
|         GC.SuppressFinalize(this); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @@ -180,6 +180,12 @@ public static class Runtime | ||||
|     #endregion | ||||
|  | ||||
|     #region 扩展 | ||||
|  | ||||
|     public static Int64 AppStartTick = TickCount64; | ||||
|  | ||||
|     /// <summary>软件启动以来的毫秒数</summary> | ||||
|     public static Int64 AppTickCount64 => TickCount64-AppStartTick; | ||||
|  | ||||
| #if NETCOREAPP3_1_OR_GREATER | ||||
|     /// <summary>系统启动以来的毫秒数</summary> | ||||
|     public static Int64 TickCount64 => Environment.TickCount64; | ||||
| @@ -196,6 +202,8 @@ public static class Runtime | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary>获取当前UTC时间。基于全局时间提供者,在星尘应用中会屏蔽服务器时间差</summary> | ||||
|     /// <returns></returns> | ||||
|     public static DateTimeOffset UtcNow => TimerScheduler.GlobalTimeProvider.GetUtcNow(); | ||||
| @@ -248,7 +256,7 @@ public static class Runtime | ||||
|  | ||||
|         return dic; | ||||
|     } | ||||
|     #endregion | ||||
| #endregion | ||||
|  | ||||
|     #region 设置 | ||||
|     private static Boolean? _createConfigOnMissing; | ||||
|   | ||||
| @@ -8,8 +8,6 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using ThingsGateway.NewLife.Log; | ||||
|  | ||||
| namespace ThingsGateway.NewLife; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -92,21 +90,119 @@ public sealed class WaitLock : IDisposable | ||||
|     /// </summary> | ||||
|     public Task WaitAsync(CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return _waiterLock.WaitAsync(cancellationToken); | ||||
| #if NET6_0_OR_GREATER | ||||
|         if (cancellationToken.CanBeCanceled) | ||||
|             return WaitUntilCountOrTimeoutAsync(_waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None), Timeout.Infinite, cancellationToken); | ||||
|         //return WaitUntilAsync2(_waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None), Timeout.Infinite, cancellationToken); | ||||
|         else | ||||
|             return _waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None); | ||||
|  | ||||
| #else | ||||
|         return _waiterLock.WaitAsync(Timeout.Infinite, cancellationToken); | ||||
| #endif | ||||
|     } | ||||
|  | ||||
| #if NET6_0_OR_GREATER | ||||
|  | ||||
|  | ||||
|     //private ObjectPoolLock<ReusableCancellationTokenSource> _reusableTimeouts = new(); | ||||
|  | ||||
|     /// <summary>Performs the asynchronous wait.</summary> | ||||
|     /// <param name="asyncWaiter">The asynchronous waiter.</param> | ||||
|     /// <param name="millisecondsTimeout">The timeout.</param> | ||||
|     /// <param name="cancellationToken">The cancellation token.</param> | ||||
|     /// <returns>The task to return to the caller.</returns> | ||||
|     private Task<bool> WaitUntilCountOrTimeoutAsync(Task<bool> asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken) | ||||
|     { | ||||
|         if (millisecondsTimeout == Timeout.Infinite) | ||||
|         { | ||||
|             return (asyncWaiter.WaitAsync(cancellationToken)); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return (asyncWaiter.WaitAsync(TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     //private Task WaitUntilAsync2(Task task, int timeoutMs, CancellationToken token) | ||||
|     //{ | ||||
|     //    if (task.IsCompleted) return task; | ||||
|  | ||||
|     //    var tcs = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously); | ||||
|     //    var reusableTimeout = _reusableTimeouts.Get(); | ||||
|  | ||||
|     //    CancellationTokenRegistration ctr = default; | ||||
|  | ||||
|     //    // 超时 + 取消 Token | ||||
|     //    if (timeoutMs != Timeout.Infinite || token.CanBeCanceled) | ||||
|     //    { | ||||
|     //        var ctsToken = reusableTimeout.GetTokenSource(timeoutMs, token); | ||||
|  | ||||
|     //        ctr = ctsToken.Register(static (state, token2) => | ||||
|     //        { | ||||
|     //            var (tcs2, ctoken) = ((TaskCompletionSource<object?>, CancellationToken))state!; | ||||
|     //            if (ctoken.IsCancellationRequested) | ||||
|     //                tcs2.TrySetCanceled(ctoken); | ||||
|     //            else | ||||
|     //                tcs2.TrySetException(new TimeoutException("The operation has timed out.")); | ||||
|     //        }, (tcs, token)); | ||||
|     //    } | ||||
|  | ||||
|     //    if (task.IsCompleted) | ||||
|     //    { | ||||
|     //        _reusableTimeouts.Return(reusableTimeout); | ||||
|     //        ctr.Dispose(); | ||||
|     //        return task; | ||||
|     //    } | ||||
|  | ||||
|     //    // 监听原始任务 | ||||
|     //    task.ContinueWith(static (t, state) => | ||||
|     //    { | ||||
|     //        var (tcs2, ctr2, ctsPool, cts) = ((TaskCompletionSource<object?>, CancellationTokenRegistration, ObjectPoolLock<ReusableCancellationTokenSource>, ReusableCancellationTokenSource))state!; | ||||
|     //        try | ||||
|     //        { | ||||
|     //            if (t.IsCanceled) | ||||
|     //                tcs2.TrySetCanceled(); | ||||
|     //            else if (t.IsFaulted) | ||||
|     //                tcs2.TrySetException(t.Exception!.InnerExceptions); | ||||
|     //            else | ||||
|     //                tcs2.TrySetResult(null); | ||||
|     //        } | ||||
|     //        finally | ||||
|     //        { | ||||
|     //            ctsPool.Return(cts); | ||||
|     //            ctr2.Dispose(); | ||||
|     //        } | ||||
|     //    }, (tcs, ctr, _reusableTimeouts, reusableTimeout), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); | ||||
|  | ||||
|     //    return tcs.Task; | ||||
|     //} | ||||
|  | ||||
| #endif | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 进入锁 | ||||
|     /// </summary> | ||||
|     public Task<bool> WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken = default) | ||||
|     { | ||||
| #if NET6_0_OR_GREATER | ||||
|         if (cancellationToken.CanBeCanceled || millisecondsTimeout != Timeout.Infinite) | ||||
|             return WaitUntilCountOrTimeoutAsync(_waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None), millisecondsTimeout, cancellationToken); | ||||
|         else | ||||
|             return _waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None); | ||||
|  | ||||
| #else | ||||
|         return _waiterLock.WaitAsync(millisecondsTimeout, cancellationToken); | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     bool DisposedValue; | ||||
|     public void Dispose() | ||||
|     { | ||||
|         DisposedValue = true; | ||||
| #if NET6_0_OR_GREATER | ||||
|         //_reusableTimeouts?.TryDispose(); | ||||
| #endif | ||||
|         _waiterLock?.TryDispose(); | ||||
|         GC.SuppressFinalize(this); | ||||
|     } | ||||
|   | ||||
| @@ -168,8 +168,8 @@ public class CompositeConfigProvider : IConfigProvider | ||||
|     #endregion | ||||
|  | ||||
|     #region 绑定 | ||||
|     private readonly ConcurrentDictionary<Object, String> _models = []; | ||||
|     private readonly ConcurrentDictionary<Object, ModelWrap> _models2 = []; | ||||
|     private readonly NonBlockingDictionary<Object, String> _models = []; | ||||
|     private readonly NonBlockingDictionary<Object, ModelWrap> _models2 = []; | ||||
|     /// <summary>绑定模型,使能热更新,配置存储数据改变时同步修改模型属性</summary> | ||||
|     /// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam> | ||||
|     /// <param name="model">模型实例</param> | ||||
|   | ||||
| @@ -11,7 +11,7 @@ public static class DictionaryExtensions | ||||
|     /// <param name="dict"></param> | ||||
|     /// <param name="key"></param> | ||||
|     /// <returns></returns> | ||||
|     public static Boolean Remove<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dict, TKey key) where TKey : notnull => dict.TryRemove(key, out _); | ||||
|     public static Boolean Remove<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dict, TKey key) where TKey : notnull => dict.TryRemove(key, out _); | ||||
|  | ||||
| #if !NET6_0_OR_GREATER | ||||
|     public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> pairs, TKey key, TValue value) | ||||
| @@ -77,7 +77,7 @@ public static class DictionaryExtensions | ||||
|     /// <summary> | ||||
|     /// 批量出队 | ||||
|     /// </summary> | ||||
|     public static List<T> ToListWithDequeue<TKEY, T>(this ConcurrentDictionary<TKEY, T> values, int maxCount = 0) | ||||
|     public static List<T> ToListWithDequeue<TKEY, T>(this NonBlockingDictionary<TKEY, T> values, int maxCount = 0) | ||||
|     { | ||||
|         if (maxCount <= 0) | ||||
|         { | ||||
| @@ -105,7 +105,7 @@ public static class DictionaryExtensions | ||||
|     /// <summary> | ||||
|     /// 批量出队 | ||||
|     /// </summary> | ||||
|     public static Dictionary<TKEY, T> ToDictWithDequeue<TKEY, T>(this ConcurrentDictionary<TKEY, T> values, int maxCount = 0) | ||||
|     public static Dictionary<TKEY, T> ToDictWithDequeue<TKEY, T>(this NonBlockingDictionary<TKEY, T> values, int maxCount = 0) | ||||
|     { | ||||
|         if (maxCount <= 0) | ||||
|         { | ||||
| @@ -135,7 +135,7 @@ public static class DictionaryExtensions | ||||
|     /// <summary> | ||||
|     /// 批量出队 | ||||
|     /// </summary> | ||||
|     public static IEnumerable<KeyValuePair<TKEY, T>> ToIEnumerableKVWithDequeue<TKEY, T>(this ConcurrentDictionary<TKEY, T> values, int maxCount = 0) | ||||
|     public static IEnumerable<KeyValuePair<TKEY, T>> ToIEnumerableKVWithDequeue<TKEY, T>(this NonBlockingDictionary<TKEY, T> values, int maxCount = 0) | ||||
|     { | ||||
|         if (values.IsEmpty) yield break; | ||||
|  | ||||
|   | ||||
| @@ -101,7 +101,7 @@ public static class LinqExtensions | ||||
|     /// <summary> | ||||
|     /// 从并发字典中删除 | ||||
|     /// </summary> | ||||
|     public static bool Remove<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dict, TKey key) where TKey : notnull | ||||
|     public static bool Remove<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dict, TKey key) where TKey : notnull | ||||
|     { | ||||
|         return dict.TryRemove(key, out TValue? _); | ||||
|     } | ||||
|   | ||||
| @@ -8,8 +8,6 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using ThingsGateway.NewLife; | ||||
|  | ||||
| namespace ThingsGateway; | ||||
|  | ||||
| /// <inheritdoc/> | ||||
|   | ||||
| @@ -97,7 +97,7 @@ public class ConsoleLog : Logger | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static readonly ConcurrentDictionary<Int32, ConsoleColor> dic = new(); | ||||
|     static readonly NonBlockingDictionary<Int32, ConsoleColor> dic = new(); | ||||
|     static readonly ConsoleColor[] colors = [ | ||||
|         ConsoleColor.Green, ConsoleColor.Cyan, ConsoleColor.Magenta, ConsoleColor.White, ConsoleColor.Yellow, | ||||
|         ConsoleColor.DarkGreen, ConsoleColor.DarkCyan, ConsoleColor.DarkMagenta, ConsoleColor.DarkRed, ConsoleColor.DarkYellow ]; | ||||
|   | ||||
| @@ -109,7 +109,7 @@ public class DefaultTracer : DisposeBase, ITracer, ILogFeature | ||||
|     public IPool<ISpan> SpanPool => _SpanPool ??= new MySpanPool(); | ||||
|  | ||||
|     /// <summary>Span构建器集合</summary> | ||||
|     protected ConcurrentDictionary<String, ISpanBuilder> _builders = new(); | ||||
|     protected NonBlockingDictionary<String, ISpanBuilder> _builders = new(); | ||||
|  | ||||
|     /// <summary>采样定时器</summary> | ||||
|     protected TimerX? _timer; | ||||
| @@ -292,7 +292,7 @@ public class DefaultTracer : DisposeBase, ITracer, ILogFeature | ||||
|         var bs = _builders; | ||||
|         if (bs.IsEmpty) return []; | ||||
|  | ||||
|         _builders = new ConcurrentDictionary<String, ISpanBuilder>(); | ||||
|         _builders = new NonBlockingDictionary<String, ISpanBuilder>(); | ||||
|  | ||||
|         var bs2 = bs.Values.Where(e => e.Total > 0).ToArray(); | ||||
|  | ||||
|   | ||||
| @@ -61,10 +61,11 @@ 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 ConcurrentDictionary<String, TextFileLog> cache = new(StringComparer.OrdinalIgnoreCase); | ||||
|     private static readonly NonBlockingDictionary<String, TextFileLog> cache = new(StringComparer.OrdinalIgnoreCase); | ||||
|     /// <summary>每个目录的日志实例应该只有一个,所以采用静态创建</summary> | ||||
|     /// <param name="path">日志目录或日志文件路径</param> | ||||
|     /// <param name="fileFormat"></param> | ||||
| @@ -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(); | ||||
|  | ||||
|         Directory.CreateDirectory(LogPath); | ||||
|         // 目录多日志文件 | ||||
|         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) | ||||
|     { | ||||
| @@ -234,46 +276,39 @@ public class TextFileLog : Logger, IDisposable | ||||
|         if (!_isFile && Backups > 0) | ||||
|         { | ||||
|             // 判断日志目录是否已存在 | ||||
|             var di = LogPath.GetBasePath().AsDirectory(); | ||||
|             DirectoryInfo? di = new DirectoryInfo(LogPath); | ||||
|             if (di.Exists) | ||||
|             { | ||||
|                 // 删除*.del | ||||
|                 // 删除 *.del | ||||
|                 try | ||||
|                 { | ||||
|                     var dels = di.GetFiles("*.del"); | ||||
|                     if (dels?.Length > 0) | ||||
|                     foreach (var item in di.EnumerateFiles("*.del")) | ||||
|                     { | ||||
|                         foreach (var item in dels) | ||||
|                         { | ||||
|                             item.Delete(); | ||||
|                         } | ||||
|                         item.Delete(); | ||||
|                     } | ||||
|                 } | ||||
|                 catch { } | ||||
|  | ||||
|                 var ext = Path.GetExtension(FileFormat); | ||||
|                 var fis = di.GetFiles("*" + ext); | ||||
|                 if (fis != null && fis.Length > Backups) | ||||
|                 var fis = di.EnumerateFiles($"*{ext}") | ||||
|                            .OrderByDescending(e => e.LastWriteTimeUtc) | ||||
|                            .Skip(Backups); | ||||
|  | ||||
|                 foreach (var item in fis) | ||||
|                 { | ||||
|                     // 删除最旧的文件 | ||||
|                     var retain = fis.Length - Backups; | ||||
|                     fis = fis.OrderBy(e => e.CreationTime).Take(retain).ToArray(); | ||||
|                     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); | ||||
|                     try | ||||
|                     { | ||||
|                         item.Delete(); | ||||
|                     } | ||||
|                     catch | ||||
|                     { | ||||
|                         OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length); | ||||
|                         try | ||||
|                         { | ||||
|                             item.Delete(); | ||||
|                             item.MoveTo(item.FullName + ".del"); | ||||
|                         } | ||||
|                         catch | ||||
|                         { | ||||
|                             try | ||||
|                             { | ||||
|                                 item.MoveTo(item.FullName + ".del"); | ||||
|                             } | ||||
|                             catch | ||||
|                             { | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| @@ -323,7 +358,6 @@ public class TextFileLog : Logger, IDisposable | ||||
|         // 推入队列 | ||||
|         Enqueue($"{e.GetAndReset()}"); | ||||
|  | ||||
|         WriteLog(); | ||||
|     } | ||||
|  | ||||
|     protected bool Check() | ||||
| @@ -340,35 +374,17 @@ 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 | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     WriteFile(); | ||||
|                 } | ||||
|                 finally | ||||
|                 { | ||||
|                     _writing = 0; | ||||
|                 } | ||||
|                 if (!_Logs.IsEmpty) WriteFile(); | ||||
|             } | ||||
|             else | ||||
|             finally | ||||
|             { | ||||
|                 ThreadPool.UnsafeQueueUserWorkItem(s => | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         WriteFile(); | ||||
|                     } | ||||
|                     catch { } | ||||
|                     finally | ||||
|                     { | ||||
|                         _writing = 0; | ||||
|                     } | ||||
|                 }, null); | ||||
|                 _writing = 0; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -80,13 +80,21 @@ 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 构造 | ||||
|  | ||||
|     static XTrace() | ||||
|     { | ||||
|         _ = Runtime.AppTickCount64; | ||||
|         AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; | ||||
|         TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; | ||||
|         AppDomain.CurrentDomain.ProcessExit += OnProcessExit; | ||||
|   | ||||
| @@ -59,7 +59,7 @@ public interface IEventHandler<TEvent> | ||||
| /// </remarks> | ||||
| public class EventBus<TEvent> : DisposeBase, IEventBus<TEvent> | ||||
| { | ||||
|     private readonly ConcurrentDictionary<String, IEventHandler<TEvent>> _handlers = []; | ||||
|     private readonly NonBlockingDictionary<String, IEventHandler<TEvent>> _handlers = []; | ||||
|     /// <summary>已订阅的事件处理器集合</summary> | ||||
|     public IDictionary<String, IEventHandler<TEvent>> Handlers => _handlers; | ||||
|  | ||||
|   | ||||
| @@ -20,9 +20,9 @@ public class DeferredQueue : DisposeBase | ||||
|     /// <summary>名称</summary> | ||||
|     public String Name { get; set; } | ||||
|  | ||||
|     private volatile ConcurrentDictionary<String, Object> _Entities = new(); | ||||
|     private volatile NonBlockingDictionary<String, Object> _Entities = new(); | ||||
|     /// <summary>实体字典</summary> | ||||
|     public ConcurrentDictionary<String, Object> Entities => _Entities; | ||||
|     public NonBlockingDictionary<String, Object> Entities => _Entities; | ||||
|  | ||||
|     /// <summary>跟踪数。达到该值时输出跟踪日志,默认1000</summary> | ||||
|     public Int32 TraceCount { get; set; } = 1000; | ||||
| @@ -206,7 +206,7 @@ public class DeferredQueue : DisposeBase | ||||
|         var es = _Entities; | ||||
|         if (es.IsEmpty) return; | ||||
|  | ||||
|         _Entities = new ConcurrentDictionary<String, Object>(); | ||||
|         _Entities = new NonBlockingDictionary<String, Object>(); | ||||
|         var times = _Times; | ||||
|  | ||||
|         Interlocked.Add(ref _count, -es.Count); | ||||
|   | ||||
| @@ -18,7 +18,7 @@ class MyServiceScope : IServiceScope, IServiceProvider | ||||
|  | ||||
|     public IServiceProvider ServiceProvider => this; | ||||
|  | ||||
|     private readonly ConcurrentDictionary<Type, Object?> _cache = new(); | ||||
|     private readonly NonBlockingDictionary<Type, Object?> _cache = new(); | ||||
|  | ||||
|     public void Dispose() | ||||
|     { | ||||
|   | ||||
| @@ -23,7 +23,7 @@ public class DnsResolver : IDnsResolver | ||||
|     /// <summary>缓存超时时间</summary> | ||||
|     public TimeSpan Expire { set; get; } = TimeSpan.FromMinutes(5); | ||||
|  | ||||
|     private readonly ConcurrentDictionary<String, DnsItem> _cache = new(); | ||||
|     private readonly NonBlockingDictionary<String, DnsItem> _cache = new(); | ||||
|  | ||||
|     /// <summary>解析域名</summary> | ||||
|     /// <param name="host"></param> | ||||
|   | ||||
| @@ -146,7 +146,7 @@ public class NetServer : DisposeBase, IServer, IExtend, ILogFeature | ||||
|     /// </remarks> | ||||
|     public IServiceProvider? ServiceProvider { get; set; } | ||||
|  | ||||
|     private ConcurrentDictionary<String, Object?>? _items; | ||||
|     private NonBlockingDictionary<String, Object?>? _items; | ||||
|     /// <summary>数据项</summary> | ||||
|     public IDictionary<String, Object?> Items => _items ??= new(); | ||||
|  | ||||
| @@ -486,7 +486,7 @@ public class NetServer : DisposeBase, IServer, IExtend, ILogFeature | ||||
|     #endregion | ||||
|  | ||||
|     #region 会话 | ||||
|     private readonly ConcurrentDictionary<Int32, INetSession> _Sessions = new(); | ||||
|     private readonly NonBlockingDictionary<Int32, INetSession> _Sessions = new(); | ||||
|     /// <summary>会话集合。用自增的数字ID作为标识,业务应用自己维持ID与业务主键的对应关系。</summary> | ||||
|     public IDictionary<Int32, INetSession> Sessions => _Sessions; | ||||
|  | ||||
|   | ||||
| @@ -853,7 +853,7 @@ public abstract class SessionBase : DisposeBase, ISocketClient, ITransport, ILog | ||||
|     #endregion 异常处理 | ||||
|  | ||||
|     #region 扩展接口 | ||||
|     private ConcurrentDictionary<String, Object?>? _items; | ||||
|     private NonBlockingDictionary<String, Object?>? _items; | ||||
|     /// <summary>数据项</summary> | ||||
|     public IDictionary<String, Object?> Items => _items ??= new(); | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ namespace ThingsGateway.NewLife.Net; | ||||
| internal class SessionCollection : DisposeBase, IDictionary<String, ISocketSession> | ||||
| { | ||||
|     #region 属性 | ||||
|     private readonly ConcurrentDictionary<String, ISocketSession> _dic = new(); | ||||
|     private readonly NonBlockingDictionary<String, ISocketSession> _dic = new(); | ||||
|  | ||||
|     /// <summary>服务端</summary> | ||||
|     public ISocketServer Server { get; private set; } | ||||
|   | ||||
| @@ -400,7 +400,7 @@ public class UdpSession : DisposeBase, ISocketSession, ITransport, ILogFeature | ||||
|     #endregion | ||||
|  | ||||
|     #region 扩展接口 | ||||
|     private ConcurrentDictionary<String, Object?>? _items; | ||||
|     private NonBlockingDictionary<String, Object?>? _items; | ||||
|     /// <summary>数据项</summary> | ||||
|     public IDictionary<String, Object?> Items => _items ??= new(); | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,15 @@ | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Provides async/await-related extension methods | ||||
|     /// </summary> | ||||
|     public static class AwaitableExtensions | ||||
|     { | ||||
|         /// <summary>Controls whether a yield operation should respect captured context</summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static ConfiguredYieldAwaitable ConfigureAwait(this YieldAwaitable _, bool continueOnCapturedContext) | ||||
|             => new ConfiguredYieldAwaitable(continueOnCapturedContext); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,125 @@ | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary>Provides an awaitable context for switching into a target environment.</summary> | ||||
|     public readonly struct ConfiguredYieldAwaitable | ||||
|     { | ||||
|         /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||
|         public override bool Equals(object? obj) => obj is ConfiguredYieldAwaitable other && other._continueOnCapturedContext == _continueOnCapturedContext; | ||||
|         /// <summary><see cref="Object.GetHashCode"/></summary> | ||||
|         public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0; | ||||
|         /// <summary><see cref="Object.ToString"/></summary> | ||||
|         public override string ToString() => nameof(ConfiguredYieldAwaitable); | ||||
|  | ||||
|         private readonly bool _continueOnCapturedContext; | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal ConfiguredYieldAwaitable(bool continueOnCapturedContext) | ||||
|             => _continueOnCapturedContext = continueOnCapturedContext; | ||||
|  | ||||
|         /// <summary>Gets an awaiter for this <see cref="ConfiguredYieldAwaitable"/>.</summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public ConfiguredYieldAwaiter GetAwaiter() | ||||
|             => new ConfiguredYieldAwaiter(_continueOnCapturedContext); | ||||
|  | ||||
|         /// <summary>Provides an awaitable context for switching into a target environment.</summary> | ||||
|         public readonly struct ConfiguredYieldAwaiter : ICriticalNotifyCompletion, INotifyCompletion | ||||
|         { | ||||
|             /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||
|             public override bool Equals(object? obj) => obj is ConfiguredYieldAwaiter other && other._continueOnCapturedContext == _continueOnCapturedContext; | ||||
|             /// <summary><see cref="Object.GetHashCode"/></summary> | ||||
|             public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0; | ||||
|             /// <summary><see cref="Object.ToString"/></summary> | ||||
|             public override string ToString() => nameof(ConfiguredYieldAwaiter); | ||||
|  | ||||
|             private readonly bool _continueOnCapturedContext; | ||||
|  | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             internal ConfiguredYieldAwaiter(bool continueOnCapturedContext) | ||||
|                 => _continueOnCapturedContext = continueOnCapturedContext; | ||||
|  | ||||
|             /// <summary>Gets whether a yield is not required.</summary> | ||||
|             public bool IsCompleted | ||||
|             { | ||||
|                 [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|                 get => false; | ||||
|             } | ||||
|  | ||||
|             /// <summary>Ends the await operation.</summary> | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             public void GetResult() { } | ||||
|  | ||||
|             /// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary> | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             public void OnCompleted(Action continuation) | ||||
|             { | ||||
|                 if (_continueOnCapturedContext) YieldFlowContext(continuation, true); | ||||
|                 else YieldNoContext(continuation, true); | ||||
|             } | ||||
|  | ||||
|             /// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary> | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             public void UnsafeOnCompleted(Action continuation) | ||||
|             { | ||||
|                 if (_continueOnCapturedContext) YieldFlowContext(continuation, false); | ||||
|                 else YieldNoContext(continuation, false); | ||||
|             } | ||||
|  | ||||
|             private static readonly WaitCallback s_waitCallbackRunAction = state => ((Action?)state)?.Invoke(); | ||||
|  | ||||
| #if PLAT_THREADPOOLWORKITEM | ||||
|             private sealed class ContinuationWorkItem : IThreadPoolWorkItem | ||||
|             { | ||||
|                 private Action? _continuation; | ||||
|  | ||||
|                 [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|                 private ContinuationWorkItem() => Internal.Counters.ItemBoxAllocated.Increment(); | ||||
|  | ||||
|                 [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|                 public static ContinuationWorkItem Create(Action continuation) | ||||
|                 { | ||||
|                     var box = Pool<ContinuationWorkItem>.TryGet() ?? new ContinuationWorkItem(); | ||||
|                     box._continuation = continuation; | ||||
|                     return box; | ||||
|                 } | ||||
|  | ||||
|                 [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|                 void IThreadPoolWorkItem.Execute() | ||||
|                 { | ||||
|                     var callback = _continuation; | ||||
|                     _continuation = null; | ||||
|                     Pool<ContinuationWorkItem>.TryPut(this); | ||||
|                     callback?.Invoke(); | ||||
|                 } | ||||
|             } | ||||
| #endif | ||||
|             [MethodImpl(MethodImplOptions.NoInlining)] // no-one ever calls ConfigureAwait(true)! | ||||
|             private static void YieldFlowContext(Action continuation, bool flowContext) | ||||
|             { | ||||
|                 var awaiter = default(YieldAwaitable.YieldAwaiter); | ||||
|                 if (flowContext) awaiter.OnCompleted(continuation); | ||||
|                 else awaiter.UnsafeOnCompleted(continuation); | ||||
|             } | ||||
|  | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             private static void YieldNoContext(Action continuation, bool flowContext) | ||||
|             { | ||||
|                 if (flowContext) | ||||
|                 { | ||||
|                     ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
| #if PLAT_THREADPOOLWORKITEM | ||||
|                     ThreadPool.UnsafeQueueUserWorkItem(ContinuationWorkItem.Create(continuation), false); | ||||
| #elif NETSTANDARD1_3 | ||||
|                     ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation); | ||||
| #else | ||||
|                     ThreadPool.UnsafeQueueUserWorkItem(s_waitCallbackRunAction, continuation); | ||||
| #endif | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user