mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-11-04 17:43:58 +08:00 
			
		
		
		
	Compare commits
	
		
			47 Commits
		
	
	
		
			5ee8b50a92
			...
			10.12.16.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					c93c80468c | ||
| 
						 | 
					a464594885 | ||
| 
						 | 
					b42f8afa35 | ||
| 
						 | 
					facf8bd401 | ||
| 
						 | 
					feeb17eca3 | ||
| 
						 | 
					b3405cd674 | ||
| 
						 | 
					c35f9cef93 | ||
| 
						 | 
					3f382202db | ||
| 
						 | 
					2a3493cc82 | ||
| 
						 | 
					aaa459ebe0 | ||
| 
						 | 
					51a8acbc3e | ||
| 
						 | 
					dc132a1999 | ||
| 
						 | 
					0c31cfcbc5 | ||
| 
						 | 
					0e44bc67cd | ||
| 
						 | 
					12dfbba42c | ||
| 
						 | 
					96b4287f3a | ||
| 
						 | 
					7d406de29f | ||
| 
						 | 
					81f0ef466a | ||
| 
						 | 
					3f2d6b133c | ||
| 
						 | 
					e776dc67eb | ||
| 
						 | 
					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 | 
							
								
								
									
										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,7 +8,8 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
using System.Runtime;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Admin.Application;
 | 
			
		||||
 | 
			
		||||
@@ -20,10 +21,6 @@ public class HardwareInfo
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public DriveInfo DriveInfo { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 硬件信息获取
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public MachineInfo? MachineInfo { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 主机环境
 | 
			
		||||
@@ -40,19 +37,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": "获取硬件信息出错"
 | 
			
		||||
 
 | 
			
		||||
@@ -10,11 +10,16 @@
 | 
			
		||||
 | 
			
		||||
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="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.3" />
 | 
			
		||||
		<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" />
 | 
			
		||||
 
 | 
			
		||||
@@ -87,42 +87,45 @@ public class Startup : AppStartup
 | 
			
		||||
#if NET8_0_OR_GREATER
 | 
			
		||||
        services
 | 
			
		||||
         .AddRazorComponents(options => options.TemporaryRedirectionUrlValidityDuration = TimeSpan.FromMinutes(10))
 | 
			
		||||
         .AddInteractiveServerComponents(options =>
 | 
			
		||||
         {
 | 
			
		||||
             options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
             options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
 | 
			
		||||
             options.MaxBufferedUnacknowledgedRenderBatches = 20;
 | 
			
		||||
             options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
 | 
			
		||||
         })
 | 
			
		||||
         .AddHubOptions(options =>
 | 
			
		||||
         {
 | 
			
		||||
             //单个传入集线器消息的最大大小。默认 32 KB
 | 
			
		||||
             options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
			
		||||
             //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
			
		||||
             options.StreamBufferCapacity = 30;
 | 
			
		||||
             options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
 | 
			
		||||
             options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
			
		||||
             options.HandshakeTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
         });
 | 
			
		||||
            .AddInteractiveServerComponents(options =>
 | 
			
		||||
            {
 | 
			
		||||
                options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
                options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
                options.MaxBufferedUnacknowledgedRenderBatches = 5;
 | 
			
		||||
                options.DisconnectedCircuitMaxRetained = 1;
 | 
			
		||||
                options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
			
		||||
            })
 | 
			
		||||
            .AddHubOptions(options =>
 | 
			
		||||
            {
 | 
			
		||||
                //单个传入集线器消息的最大大小。默认 32 KB
 | 
			
		||||
                options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
			
		||||
                //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
			
		||||
                options.StreamBufferCapacity = 30;
 | 
			
		||||
                options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
 | 
			
		||||
                options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
			
		||||
                options.HandshakeTimeout = TimeSpan.FromSeconds(15);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
        services.AddServerSideBlazor(options =>
 | 
			
		||||
        {
 | 
			
		||||
            options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
            options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
 | 
			
		||||
            options.MaxBufferedUnacknowledgedRenderBatches = 20;
 | 
			
		||||
            options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
 | 
			
		||||
        }).AddHubOptions(options =>
 | 
			
		||||
        {
 | 
			
		||||
            //单个传入集线器消息的最大大小。默认 32 KB
 | 
			
		||||
            options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
			
		||||
            //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
			
		||||
            options.StreamBufferCapacity = 30;
 | 
			
		||||
            options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
 | 
			
		||||
            options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
			
		||||
            options.HandshakeTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
        });
 | 
			
		||||
             {
 | 
			
		||||
                 options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
                 options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
                 options.MaxBufferedUnacknowledgedRenderBatches = 5;
 | 
			
		||||
                 options.DisconnectedCircuitMaxRetained = 1;
 | 
			
		||||
                 options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
			
		||||
             })
 | 
			
		||||
             .AddHubOptions(options =>
 | 
			
		||||
             {
 | 
			
		||||
                 //单个传入集线器消息的最大大小。默认 32 KB
 | 
			
		||||
                 options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
 | 
			
		||||
                 //可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
 | 
			
		||||
                 options.StreamBufferCapacity = 30;
 | 
			
		||||
                 options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
 | 
			
		||||
                 options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
			
		||||
                 options.HandshakeTimeout = TimeSpan.FromSeconds(15);
 | 
			
		||||
             });
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ using System.ComponentModel.DataAnnotations;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Common.Extension;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#if NET8_0_OR_GREATER
 | 
			
		||||
using System.Collections.Frozen;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -27,7 +28,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 +41,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 +105,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 +205,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 +213,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 +222,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 +238,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)
 | 
			
		||||
        {
 | 
			
		||||
@@ -287,8 +256,15 @@ internal class CacheManager
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="assembly">Assembly 程序集实例</param>
 | 
			
		||||
    /// <param name="typeName">类型名称</param>
 | 
			
		||||
    public static IEnumerable<LocalizedString>? GetAllStringsByTypeName(Assembly assembly, string typeName)
 | 
			
		||||
    public static FrozenSet<LocalizedString>? GetAllStringsByTypeName(Assembly assembly, string typeName)
 | 
			
		||||
        => GetJsonStringByTypeName(GetJsonLocalizationOption(), assembly, typeName, CultureInfo.CurrentUICulture.Name);
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取指定文化本地化资源集合
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="assembly">Assembly 程序集实例</param>
 | 
			
		||||
    /// <param name="typeName">类型名称</param>
 | 
			
		||||
    public static FrozenDictionary<string, string>? GetAllHasValueStringsByTypeName(Assembly assembly, string typeName)
 | 
			
		||||
        => GetHasValueJsonStringByTypeName(GetJsonLocalizationOption(), assembly, typeName, CultureInfo.CurrentUICulture.Name);
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 通过指定程序集获取所有本地化信息键值集合
 | 
			
		||||
@@ -299,7 +275,7 @@ internal class CacheManager
 | 
			
		||||
    /// <param name="cultureName">cultureName 未空时使用 CultureInfo.CurrentUICulture.Name</param>
 | 
			
		||||
    /// <param name="forceLoad">默认 false 使用缓存值 设置 true 时内部强制重新加载</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static IEnumerable<LocalizedString>? GetJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false)
 | 
			
		||||
    public static FrozenSet<LocalizedString>? GetJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false)
 | 
			
		||||
    {
 | 
			
		||||
        if (assembly.IsDynamic)
 | 
			
		||||
        {
 | 
			
		||||
@@ -309,13 +285,15 @@ internal class CacheManager
 | 
			
		||||
        cultureName ??= CultureInfo.CurrentUICulture.Name;
 | 
			
		||||
        if (string.IsNullOrEmpty(cultureName))
 | 
			
		||||
        {
 | 
			
		||||
            return [];
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var key = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}";
 | 
			
		||||
        var typeKey = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{typeName}-{cultureName}";
 | 
			
		||||
        if (forceLoad)
 | 
			
		||||
        {
 | 
			
		||||
            Instance.Cache.Remove(key);
 | 
			
		||||
            Instance.Cache.Remove(typeKey);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var localizedItems = Instance.GetOrCreate(key, entry =>
 | 
			
		||||
@@ -336,16 +314,77 @@ internal class CacheManager
 | 
			
		||||
            return items.ToHashSet();
 | 
			
		||||
#endif
 | 
			
		||||
        });
 | 
			
		||||
        return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        var typeLocalizedItems = Instance.GetOrCreate(typeKey, entry =>
 | 
			
		||||
        {
 | 
			
		||||
            return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase)).ToFrozenSet();
 | 
			
		||||
        });
 | 
			
		||||
        return typeLocalizedItems;
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 通过 ILocalizationResolve 接口实现类获得本地化键值集合
 | 
			
		||||
    /// 通过指定程序集获取所有本地化信息键值集合
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="typeName"></param>
 | 
			
		||||
    /// <param name="includeParentCultures"></param>
 | 
			
		||||
    /// <param name="option">JsonLocalizationOptions 实例</param>
 | 
			
		||||
    /// <param name="assembly">Assembly 程序集实例</param>
 | 
			
		||||
    /// <param name="typeName">类型名称</param>
 | 
			
		||||
    /// <param name="cultureName">cultureName 未空时使用 CultureInfo.CurrentUICulture.Name</param>
 | 
			
		||||
    /// <param name="forceLoad">默认 false 使用缓存值 设置 true 时内部强制重新加载</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Instance.Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
 | 
			
		||||
    public static FrozenDictionary<string, string>? GetHasValueJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false)
 | 
			
		||||
    {
 | 
			
		||||
        if (assembly.IsDynamic)
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cultureName ??= CultureInfo.CurrentUICulture.Name;
 | 
			
		||||
        if (string.IsNullOrEmpty(cultureName))
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var key = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var typeKey = $"{CacheKeyPrefix}-{nameof(GetHasValueJsonStringByTypeName)}-{assembly.GetUniqueName()}-{typeName}-{cultureName}";
 | 
			
		||||
        if (forceLoad)
 | 
			
		||||
        {
 | 
			
		||||
            Instance.Cache.Remove(key);
 | 
			
		||||
            Instance.Cache.Remove(typeKey);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var localizedItems = Instance.GetOrCreate(key, entry =>
 | 
			
		||||
        {
 | 
			
		||||
            var sections = option.GetJsonStringFromAssembly(assembly, cultureName);
 | 
			
		||||
            var items = sections.SelectMany(section => section.GetChildren().Select(kv =>
 | 
			
		||||
            {
 | 
			
		||||
                var value = kv.Value;
 | 
			
		||||
                if (value == null && option.UseKeyWhenValueIsNull == true)
 | 
			
		||||
                {
 | 
			
		||||
                    value = kv.Key;
 | 
			
		||||
                }
 | 
			
		||||
                return new LocalizedString(kv.Key, value ?? "", false, section.Key);
 | 
			
		||||
            }));
 | 
			
		||||
#if NET8_0_OR_GREATER
 | 
			
		||||
            return items.ToFrozenSet();
 | 
			
		||||
#else
 | 
			
		||||
            return items.ToHashSet();
 | 
			
		||||
#endif
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var typeLocalizedItems = Instance.GetOrCreate(typeKey, entry =>
 | 
			
		||||
        {
 | 
			
		||||
            return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase) && !item.ResourceNotFound).ToFrozenDictionary(a => a.Name, a => a.Value);
 | 
			
		||||
        });
 | 
			
		||||
        return typeLocalizedItems;
 | 
			
		||||
    }
 | 
			
		||||
    ///// <summary>
 | 
			
		||||
    ///// 通过 ILocalizationResolve 接口实现类获得本地化键值集合
 | 
			
		||||
    ///// </summary>
 | 
			
		||||
    ///// <param name="typeName"></param>
 | 
			
		||||
    ///// <param name="includeParentCultures"></param>
 | 
			
		||||
    ///// <returns></returns>
 | 
			
		||||
    //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;
 | 
			
		||||
            }
 | 
			
		||||
@@ -80,50 +81,16 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    private string? GetStringSafely(string name) => GetStringFromJson(name);
 | 
			
		||||
 | 
			
		||||
    private string? GetStringFromService(string name)
 | 
			
		||||
    {
 | 
			
		||||
        // get string from inject service
 | 
			
		||||
        string? ret = null;
 | 
			
		||||
        if (jsonLocalizationOptions.DisableGetLocalizerFromService == false)
 | 
			
		||||
        {
 | 
			
		||||
            var localizer = Utility.GetStringLocalizerFromService(Assembly, typeName);
 | 
			
		||||
            if (localizer != null && localizer is not JsonStringLocalizer)
 | 
			
		||||
            {
 | 
			
		||||
                var l = localizer[name];
 | 
			
		||||
                if (!l.ResourceNotFound)
 | 
			
		||||
                {
 | 
			
		||||
                    ret = l.Value;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private string? GetStringFromResourceManager(string name)
 | 
			
		||||
    {
 | 
			
		||||
        string? ret = null;
 | 
			
		||||
        if (jsonLocalizationOptions.DisableGetLocalizerFromResourceManager == false)
 | 
			
		||||
        {
 | 
			
		||||
            ret = GetStringSafely(name, CultureInfo.CurrentUICulture);
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private readonly ConcurrentHashSet<string> _missingManifestCache = [];
 | 
			
		||||
    private string? GetStringFromJson(string name)
 | 
			
		||||
    {
 | 
			
		||||
        // get string from json localization file
 | 
			
		||||
        var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(Assembly, typeName));
 | 
			
		||||
        var cacheKey = $"name={name}&culture={CultureInfo.CurrentUICulture.Name}";
 | 
			
		||||
        string? ret = null;
 | 
			
		||||
        if (!_missingManifestCache.Contain(cacheKey))
 | 
			
		||||
        {
 | 
			
		||||
            var l = localizerStrings.Find(i => i.Name == name);
 | 
			
		||||
            if (l is { ResourceNotFound: false })
 | 
			
		||||
            {
 | 
			
		||||
                ret = l.Value;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            var localizerStrings = CacheManager.GetAllHasValueStringsByTypeName(Assembly, typeName);
 | 
			
		||||
            if (localizerStrings?.TryGetValue(name, out ret) != true)
 | 
			
		||||
            {
 | 
			
		||||
                // 如果没有找到资源信息则尝试从父类中查找
 | 
			
		||||
                ret ??= GetStringFromBaseType(name);
 | 
			
		||||
@@ -149,39 +116,25 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
 | 
			
		||||
            if (baseType != type)
 | 
			
		||||
            {
 | 
			
		||||
                var baseAssembly = baseType.Assembly;
 | 
			
		||||
                var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(baseAssembly, baseType.FullName!));
 | 
			
		||||
                var l = localizerStrings.Find(i => i.Name == name);
 | 
			
		||||
                if (l is { ResourceNotFound: false })
 | 
			
		||||
                {
 | 
			
		||||
                    ret = l.Value;
 | 
			
		||||
                }
 | 
			
		||||
                var localizerStrings = CacheManager.GetAllHasValueStringsByTypeName(baseAssembly, baseType.FullName!);
 | 
			
		||||
                _ = localizerStrings?.TryGetValue(name, out ret);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private List<LocalizedString> MegerResolveLocalizers(IEnumerable<LocalizedString>? localizerStrings)
 | 
			
		||||
    {
 | 
			
		||||
        var localizers = new List<LocalizedString>(CacheManager.GetTypeStringsFromResolve(typeName));
 | 
			
		||||
 | 
			
		||||
        if (localizerStrings != null)
 | 
			
		||||
        {
 | 
			
		||||
            localizers.AddRange(localizerStrings);
 | 
			
		||||
        }
 | 
			
		||||
        return localizers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void HandleMissingResourceItem(string name)
 | 
			
		||||
    {
 | 
			
		||||
        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}");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private List<LocalizedString>? _allLocalizerdStrings;
 | 
			
		||||
    private LocalizedString[]? _allLocalizerdStrings;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取当前语言的所有资源信息
 | 
			
		||||
@@ -196,7 +149,7 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
 | 
			
		||||
                ?? GetAllStringsFromBase()
 | 
			
		||||
                ?? GetAllStringsFromJson();
 | 
			
		||||
 | 
			
		||||
            _allLocalizerdStrings = MegerResolveLocalizers(items);
 | 
			
		||||
            _allLocalizerdStrings = items.ToArray();
 | 
			
		||||
        }
 | 
			
		||||
        return _allLocalizerdStrings;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										216
									
								
								src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,216 @@
 | 
			
		||||
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 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>基础空闲集合。只保存最小个数,最热部分</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}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 主方法
 | 
			
		||||
    /// <summary>借出</summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public virtual T Get()
 | 
			
		||||
    {
 | 
			
		||||
        T? pi = null;
 | 
			
		||||
        do
 | 
			
		||||
        {
 | 
			
		||||
            lock (lockThis)
 | 
			
		||||
            {
 | 
			
		||||
                if (_free.Count > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    pi = _free.Pop();
 | 
			
		||||
                    _FreeCount--;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    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 (lockThis)
 | 
			
		||||
        {
 | 
			
		||||
            // 加入繁忙集合
 | 
			
		||||
            _busy.Add(pi);
 | 
			
		||||
 | 
			
		||||
            _BusyCount++;
 | 
			
		||||
        }
 | 
			
		||||
        return pi;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>借出时是否可用</summary>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    protected virtual Boolean OnGet(T value) => true;
 | 
			
		||||
 | 
			
		||||
    /// <summary>归还</summary>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    public virtual Boolean Return(T value)
 | 
			
		||||
    {
 | 
			
		||||
        if (value == null) return false;
 | 
			
		||||
        lock (lockThis)
 | 
			
		||||
        {
 | 
			
		||||
            // 从繁忙队列找到并移除缓存项
 | 
			
		||||
            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 (lockThis)
 | 
			
		||||
        {
 | 
			
		||||
            _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()
 | 
			
		||||
    {
 | 
			
		||||
        lock (lockThis)
 | 
			
		||||
        {
 | 
			
		||||
            var 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -51,18 +51,20 @@ public class ExpiringDictionary<TKey, TValue> : IDisposable
 | 
			
		||||
            return rs;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private NonBlockingDictionary<TKey, CacheItem> _dict;
 | 
			
		||||
 | 
			
		||||
    private ConcurrentDictionary<TKey, CacheItem> _dict = new();
 | 
			
		||||
    private readonly TimerX _cleanupTimer;
 | 
			
		||||
    private int defaultExpire = 60;
 | 
			
		||||
    public ExpiringDictionary(int expire = 60)
 | 
			
		||||
    IEqualityComparer<TKey>? comparer;
 | 
			
		||||
    public ExpiringDictionary(int expire = 60, IEqualityComparer<TKey>? comparer = null)
 | 
			
		||||
    {
 | 
			
		||||
        defaultExpire = expire;
 | 
			
		||||
        _cleanupTimer = new TimerX(TimerClear, null, 10000, 10000) { Async = true };
 | 
			
		||||
        this.comparer = comparer;
 | 
			
		||||
        _dict = new NonBlockingDictionary<TKey, CacheItem>(comparer);
 | 
			
		||||
 | 
			
		||||
        _cleanupTimer = new TimerX(TimerClear, null, 60000, 60000) { Async = true };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public bool TryAdd(TKey key, TValue value)
 | 
			
		||||
    {
 | 
			
		||||
        if (_dict.TryGetValue(key, out var item))
 | 
			
		||||
@@ -109,7 +111,7 @@ 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)
 | 
			
		||||
@@ -143,3 +145,4 @@ public class ExpiringDictionary<TKey, TValue> : IDisposable
 | 
			
		||||
        _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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1,23 +1,27 @@
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using System.Net.NetworkInformation;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Runtime;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using System.Runtime.Versioning;
 | 
			
		||||
using System.Security;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.NewLife.Collections;
 | 
			
		||||
using ThingsGateway.NewLife.Data;
 | 
			
		||||
using ThingsGateway.NewLife.Log;
 | 
			
		||||
using ThingsGateway.NewLife.Model;
 | 
			
		||||
using ThingsGateway.NewLife.Reflection;
 | 
			
		||||
using ThingsGateway.NewLife.Serialization;
 | 
			
		||||
using ThingsGateway.NewLife.Windows;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#if NETFRAMEWORK
 | 
			
		||||
using System.Management;
 | 
			
		||||
 | 
			
		||||
using Microsoft.VisualBasic.Devices;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
#if NETFRAMEWORK || NET6_0_OR_GREATER
 | 
			
		||||
using Microsoft.Win32;
 | 
			
		||||
@@ -42,7 +46,7 @@ public interface IMachineInfo
 | 
			
		||||
/// 
 | 
			
		||||
/// 刷新信息成本较高,建议采用单例模式
 | 
			
		||||
/// </remarks>
 | 
			
		||||
public class MachineInfo : IExtend
 | 
			
		||||
public class MachineInfo
 | 
			
		||||
{
 | 
			
		||||
    #region 属性
 | 
			
		||||
    /// <summary>系统名称</summary>
 | 
			
		||||
@@ -88,11 +92,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 +120,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 全局静态
 | 
			
		||||
@@ -323,7 +412,7 @@ public class MachineInfo : IExtend
 | 
			
		||||
#if NETFRAMEWORK || WINDOWS
 | 
			
		||||
        {
 | 
			
		||||
            var ci = new Microsoft.VisualBasic.Devices.ComputerInfo();
 | 
			
		||||
            Memory = (ulong)(ci.TotalPhysicalMemory / 1024.0);
 | 
			
		||||
            Memory = (ulong)(ci.TotalPhysicalMemory / 1024.0 / 1024.0);
 | 
			
		||||
        }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -543,7 +632,7 @@ public class MachineInfo : IExtend
 | 
			
		||||
            //if (dic2.TryGetValue("Model Name", out str)) Product = str;
 | 
			
		||||
            if (dic.TryGetValue("Model Identifier", out var str)) Product = str;
 | 
			
		||||
            if (dic.TryGetValue("Processor Name", out str)) Processor = str;
 | 
			
		||||
            if (dic.TryGetValue("Memory", out str)) Memory = (UInt64)str.TrimEnd("GB").Trim().ToLong() * 1024 * 1024;
 | 
			
		||||
            if (dic.TryGetValue("Memory", out str)) Memory = (UInt64)str.TrimEnd("GB").Trim().ToLong() * 1024;
 | 
			
		||||
            if (dic.TryGetValue("Serial Number (system)", out str)) Serial = str;
 | 
			
		||||
            if (dic.TryGetValue("Hardware UUID", out str)) UUID = str;
 | 
			
		||||
            if (dic.TryGetValue("Processor Name", out str)) Processor = str;
 | 
			
		||||
@@ -569,10 +658,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)Math.Max(0, (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 +709,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 +804,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() / 1024.0);
 | 
			
		||||
 | 
			
		||||
                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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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