mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 23:53:58 +08:00 
			
		
		
		
	Compare commits
	
		
			43 Commits
		
	
	
		
			10.11.107.
			...
			10.12.18.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | e3960ce115 | ||
|   | 0cf098be85 | ||
|   | c93c80468c | ||
|   | a464594885 | ||
|   | b42f8afa35 | ||
|   | facf8bd401 | ||
|   | feeb17eca3 | ||
|   | b3405cd674 | ||
|   | c35f9cef93 | ||
|   | 3f382202db | ||
|   | 2a3493cc82 | ||
|   | aaa459ebe0 | ||
|   | 51a8acbc3e | ||
|   | dc132a1999 | ||
|   | 0c31cfcbc5 | ||
|   | 0e44bc67cd | ||
|   | 12dfbba42c | ||
|   | 96b4287f3a | ||
|   | 7d406de29f | ||
|   | 81f0ef466a | ||
|   | 3f2d6b133c | ||
|   | e776dc67eb | ||
| ![devin-ai-integration[bot]](/assets/img/avatar_default.png)  | bc5827d140 | ||
|   | 21838bf4af | ||
|   | 6090108597 | ||
|   | b47b9e6f43 | ||
|   | 18d1cffb2d | ||
|   | 516fd7f235 | ||
|   | 2ee16c3533 | ||
|   | 7d22f5c78e | ||
|   | 3e604ee2fd | ||
|   | 47e442874c | ||
|   | 2a8c0cbab1 | ||
|   | c26898b49d | ||
|   | 00c24d06a3 | ||
|   | 3461f34240 | ||
|   | aa1ce08c02 | ||
|   | 9c230c2da9 | ||
|   | 21215d0379 | ||
|   | 7448183791 | ||
|   | 35edd7dc43 | ||
|   | bd178831e3 | ||
|   | fe9ec6ad10 | 
							
								
								
									
										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 | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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!" RenderMode=RenderMode | ||||
|     <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 | ||||
|   | ||||
| @@ -13,13 +13,22 @@ 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] | ||||
| @@ -158,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); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -81,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); | ||||
| @@ -150,28 +116,13 @@ 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); | ||||
| @@ -183,7 +134,7 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | ||||
|         _missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}"); | ||||
|     } | ||||
|  | ||||
|     private List<LocalizedString>? _allLocalizerdStrings; | ||||
|     private LocalizedString[]? _allLocalizerdStrings; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取当前语言的所有资源信息 | ||||
| @@ -198,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.2" /> | ||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.11.4" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -4,7 +4,6 @@ | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| using System.Collections; | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace System.Collections.Concurrent | ||||
|   | ||||
| @@ -5,12 +5,10 @@ | ||||
| #nullable disable | ||||
| #pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. | ||||
|  | ||||
| using System.Collections; | ||||
| using System.Collections.ObjectModel; | ||||
| using System.Diagnostics; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Collections.Concurrent; | ||||
|  | ||||
| using static System.Collections.Concurrent.DictionaryImpl; | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
| @@ -62,7 +62,7 @@ public class ExpiringDictionary<TKey, TValue> : IDisposable | ||||
|         this.comparer = comparer; | ||||
|         _dict = new NonBlockingDictionary<TKey, CacheItem>(comparer); | ||||
|  | ||||
|         _cleanupTimer = new TimerX(TimerClear, null, 10000, 10000) { Async = true }; | ||||
|         _cleanupTimer = new TimerX(TimerClear, null, 60000, 60000) { Async = true }; | ||||
|     } | ||||
|  | ||||
|     public bool TryAdd(TKey key, TValue value) | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -90,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); | ||||
|     } | ||||
|   | ||||
| @@ -61,7 +61,8 @@ public class TextFileLog : Logger, IDisposable | ||||
|         MaxBytes = set.LogFileMaxBytes; | ||||
|         Backups = set.LogFileBackups; | ||||
|  | ||||
|         _Timer = new TimerX(DoWriteAndClose, null, 0_000, 5_000) { Async = true }; | ||||
|         _Timer = new TimerX(DoWriteAndClose, null, 0_000, 60_000, nameof(TextFileLog)) { Async = true }; | ||||
|         _WriteTimer = new TimerX(DoWrite, null, 0_000, 1000, nameof(TextFileLog)) { Async = true }; | ||||
|     } | ||||
|  | ||||
|     private static readonly NonBlockingDictionary<String, TextFileLog> cache = new(StringComparer.OrdinalIgnoreCase); | ||||
| @@ -96,6 +97,7 @@ public class TextFileLog : Logger, IDisposable | ||||
|     protected virtual void Dispose(Boolean disposing) | ||||
|     { | ||||
|         _Timer.TryDispose(); | ||||
|         _WriteTimer.TryDispose(); | ||||
|  | ||||
|         // 销毁前把队列日志输出 | ||||
|         if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0) WriteAndClose(DateTime.MinValue); | ||||
| @@ -147,35 +149,72 @@ public class TextFileLog : Logger, IDisposable | ||||
|  | ||||
|     /// <summary>获取日志文件路径</summary> | ||||
|     /// <returns></returns> | ||||
|     private String? GetLogFile() | ||||
|     private string? GetLogFile() | ||||
|     { | ||||
|         // 单日志文件 | ||||
|         if (_isFile) return LogPath.GetBasePath(); | ||||
|  | ||||
|         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.LastWriteTimeUtc).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; | ||||
|   | ||||
| @@ -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 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,62 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents an operation that completes at the first incomplete await, | ||||
|     /// with the remainder continuing in the background | ||||
|     /// </summary> | ||||
|     [AsyncMethodBuilder(typeof(MethodBuilders.FireAndForgetMethodBuilder))] | ||||
|     public readonly struct FireAndForget | ||||
|     { | ||||
|         /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||
|         public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>(); | ||||
|         /// <summary><see cref="Object.GetHashCode"/></summary> | ||||
|         public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>(); | ||||
|         /// <summary><see cref="Object.ToString"/></summary> | ||||
|         public override string ToString() => nameof(FireAndForget); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Raised when exceptions occur on fire-and-forget methods | ||||
|         /// </summary> | ||||
|         public static event Action<Exception>? Exception; | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal static void OnException(Exception exception) | ||||
|         { | ||||
|             if (exception != null) Exception?.Invoke(exception); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a value-task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public ValueTask AsValueTask() => default; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public Task AsTask() => TaskUtils.CompletedTask; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static implicit operator Task(FireAndForget _) => TaskUtils.CompletedTask; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a value-task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static implicit operator ValueTask(FireAndForget _) => default; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the awaiter for the instance | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public ValueTaskAwaiter GetAwaiter() => default(ValueTask).GetAwaiter(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/Admin/ThingsGateway.NewLife.X/PooledAwait/IResettable.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Admin/ThingsGateway.NewLife.X/PooledAwait/IResettable.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Indicates that an object can be reset | ||||
|     /// </summary> | ||||
|     public interface IResettable | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Resets this instance | ||||
|         /// </summary> | ||||
|         public void Reset(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,12 @@ | ||||
| #if NETSTANDARD1_3 | ||||
| using System; | ||||
|  | ||||
| namespace PooledAwait.Internal | ||||
| { | ||||
|     [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] | ||||
|     internal sealed class BrowsableAttribute : Attribute | ||||
|     { | ||||
|         public BrowsableAttribute(bool _) { } | ||||
|     } | ||||
| } | ||||
| #endif | ||||
| @@ -0,0 +1,68 @@ | ||||
| using System.Diagnostics; | ||||
|  | ||||
| namespace PooledAwait.Internal | ||||
| { | ||||
|     internal static class Counters | ||||
|     { | ||||
|         internal struct Counter | ||||
|         { | ||||
|             private long _value; | ||||
|             [Conditional("DEBUG")] | ||||
|             public void Increment() => Interlocked.Increment(ref _value); | ||||
| #if !DEBUG | ||||
|             [System.Obsolete("Release only", false)] | ||||
| #endif | ||||
|             public long Value => Interlocked.Read(ref _value); | ||||
| #pragma warning disable CS0618 | ||||
|             public override string ToString() => Value.ToString(); | ||||
| #pragma warning restore CS0618 | ||||
|             public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>(); | ||||
|             public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>(); | ||||
|             public void Reset() => Interlocked.Exchange(ref _value, 0); | ||||
|         } | ||||
|  | ||||
|         internal static Counter | ||||
|             SetStateMachine, | ||||
|             PooledStateAllocated, | ||||
|             PooledStateRecycled, | ||||
|             StateMachineBoxAllocated, | ||||
|             StateMachineBoxRecycled, | ||||
|             ItemBoxAllocated, | ||||
|             TaskAllocated, | ||||
|             LazyStateAllocated; | ||||
|  | ||||
| #if !DEBUG | ||||
|         [System.Obsolete("Release only", false)] | ||||
| #endif | ||||
|         public static long TotalAllocations => | ||||
|             PooledStateAllocated.Value + StateMachineBoxAllocated.Value | ||||
|             + ItemBoxAllocated.Value + TaskAllocated.Value | ||||
|             + SetStateMachine.Value // SetStateMachine usually means a boxed value | ||||
|             + LazyStateAllocated.Value; | ||||
|  | ||||
|         internal static void Reset() | ||||
|         { | ||||
|             SetStateMachine.Reset(); | ||||
|             PooledStateAllocated.Reset(); | ||||
|             PooledStateRecycled.Reset(); | ||||
|             StateMachineBoxAllocated.Reset(); | ||||
|             StateMachineBoxRecycled.Reset(); | ||||
|             ItemBoxAllocated.Reset(); | ||||
|             TaskAllocated.Reset(); | ||||
|             LazyStateAllocated.Reset(); | ||||
|         } | ||||
|  | ||||
| #if !DEBUG | ||||
|         [System.Obsolete("Release only", false)] | ||||
| #endif | ||||
|         internal static string Summary() | ||||
|             => $@"SetStateMachine: {SetStateMachine.Value} | ||||
| PooledStateAllocated: {PooledStateAllocated.Value} | ||||
| PooledStateRecycled: {PooledStateRecycled.Value} | ||||
| StateMachineBoxAllocated: {StateMachineBoxAllocated.Value} | ||||
| StateMachineBoxRecycled: {StateMachineBoxRecycled.Value} | ||||
| ItemBoxAllocated: {ItemBoxAllocated.Value} | ||||
| TaskAllocated: {TaskAllocated.Value} | ||||
| LazyStateAllocated: {LazyStateAllocated.Value}"; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,173 @@ | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait.Internal | ||||
| { | ||||
|     internal sealed class LazyTaskState<T> | ||||
|     { | ||||
|         private short _version; | ||||
|         private T _result; | ||||
|         private Exception? _exception; | ||||
|         private Task? _task; | ||||
|         private bool _isComplete; | ||||
|         private ValueTaskCompletionSource<T> _source; | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private void CheckTokenInsideLock(short token) | ||||
|         { | ||||
|             if (token != _version) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|         private object _syncRoot = new object(); | ||||
|         public Task GetTask(short token) | ||||
|         { | ||||
|             lock (_syncRoot) | ||||
|             { | ||||
|                 CheckTokenInsideLock(token); | ||||
|                 if (_task != null) { } | ||||
|                 else if (_exception is OperationCanceledException) _task = TaskUtils.TaskFactory<T>.Canceled; | ||||
|                 else if (_exception != null) _task = TaskUtils.FromException<T>(_exception); | ||||
|                 else if (_isComplete) _task = typeof(T) == typeof(Nothing) ? TaskUtils.CompletedTask : TaskUtils.TaskFactory<T>.FromResult(_result); | ||||
|                 else | ||||
|                 { | ||||
|                     _source = ValueTaskCompletionSource<T>.Create(); | ||||
|                     _task = _source.Task; | ||||
|                 } | ||||
|                 return _task; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         internal bool IsValid(short token) => Volatile.Read(ref _version) == token; | ||||
|         internal bool HasSource | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 lock (_syncRoot) { return !_source.IsNull; } | ||||
|             } | ||||
|         } | ||||
|         internal bool HasTask => Volatile.Read(ref _task) != null; | ||||
|  | ||||
|         public bool TrySetResult(short token, T result) | ||||
|         { | ||||
|             lock (_syncRoot) | ||||
|             { | ||||
|                 if (_isComplete) return false; | ||||
|                 if (token != _version) return false; | ||||
|                 _isComplete = true; | ||||
|                 if (!_source.IsNull) return _source.TrySetResult(result); | ||||
|                 _result = result; | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public bool TrySetException(short token, Exception exception) | ||||
|         { | ||||
|             lock (_syncRoot) | ||||
|             { | ||||
|                 if (_isComplete) return false; | ||||
|                 if (token != _version) return false; | ||||
|                 _isComplete = true; | ||||
|                 if (!_source.IsNull) return _source.TrySetException(exception); | ||||
|                 _exception = exception; | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public bool TrySetCanceled(short token, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             lock (_syncRoot) | ||||
|             { | ||||
|                 if (_isComplete) return false; | ||||
|                 if (token != _version) return false; | ||||
|                 _isComplete = true; | ||||
|                 if (!_source.IsNull) return _source.TrySetCanceled(cancellationToken); | ||||
|                 _task = TaskUtils.TaskFactory<T>.Canceled; | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static LazyTaskState<T> Create() => Pool<LazyTaskState<T>>.TryGet() ?? new LazyTaskState<T>(); | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private LazyTaskState() | ||||
|         { | ||||
|             Counters.LazyStateAllocated.Increment(); | ||||
|             _result = default!; | ||||
|             _version = InitialVersion; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static LazyTaskState<T> CreateConstant(T value) | ||||
|         { | ||||
|             var obj = new LazyTaskState<T> | ||||
|             { | ||||
|                 _version = Constant | ||||
|             }; | ||||
|             obj.TrySetResult(Constant, value); | ||||
|             return obj; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static LazyTaskState<T> CreateCanceled() | ||||
|         { | ||||
|             var obj = new LazyTaskState<T> | ||||
|             { | ||||
|                 _version = Constant | ||||
|             }; | ||||
|             obj.TrySetCanceled(Constant); | ||||
|             return obj; | ||||
|         } | ||||
|  | ||||
|         const short InitialVersion = 0, Constant = InitialVersion - 1; | ||||
|  | ||||
|         internal short Version | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _version; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.NoInlining)] | ||||
|         internal void Recycle(short token) | ||||
|         { | ||||
|             if (token == Constant) return; // never recycle constant values; this is by design | ||||
|  | ||||
|             if (Volatile.Read(ref _version) != token) return; // wrong version; all bets are off! | ||||
|  | ||||
|             if (!Volatile.Read(ref _isComplete)) // if incomplete, try to cancel | ||||
|             { | ||||
|                 if (!TrySetCanceled(token)) return; // if that didn't work... give up - don't recycle | ||||
|             } | ||||
|  | ||||
|             bool haveLock = false; | ||||
|             try | ||||
|             { | ||||
|                 // only if uncontested; we're not waiting in a dispose | ||||
|                 Monitor.TryEnter(_syncRoot, ref haveLock); | ||||
|                 if (haveLock) | ||||
|                 { | ||||
|                     if (token == _version) | ||||
|                     { | ||||
|                         _result = default!; | ||||
|                         _exception = default; | ||||
|                         _task = default; | ||||
|                         _isComplete = false; | ||||
|                         _source = default; | ||||
|  | ||||
|                         switch (++_version) | ||||
|                         { | ||||
|                             case InitialVersion: // don't wrap all the way around when recycling; could lead to conflicts | ||||
|                             case Constant: // don't allow things to *become* constants | ||||
|                                 break; | ||||
|                             default: | ||||
|                                 Pool<LazyTaskState<T>>.TryPut(this); | ||||
|                                 break; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 if (haveLock) Monitor.Exit(this); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,314 @@ | ||||
| #if !PLAT_MRVTSC | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.Runtime.ExceptionServices; | ||||
| using System.Runtime.InteropServices; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using System.Threading.Tasks.Sources; | ||||
|  | ||||
| // from: https://raw.githubusercontent.com/dotnet/coreclr/master/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/ManualResetValueTaskSourceCore.cs | ||||
| // original license: | ||||
|  | ||||
| // Licensed to the .NET Foundation under one or more agreements. | ||||
| // The .NET Foundation licenses this file to you under the MIT license. | ||||
| // See the LICENSE file in the project root for more information. | ||||
|  | ||||
| namespace PooledAwait.Internal | ||||
| { | ||||
|     internal readonly struct WaitCallbackShim | ||||
|     { | ||||
|         public readonly Action<object?>? Continuation; | ||||
|         public readonly object? State; | ||||
|         private WaitCallbackShim(Action<object?>? continuation, object? state) | ||||
|         { | ||||
|             Continuation = continuation; | ||||
|             State = state; | ||||
|         } | ||||
|         public static object Create(Action<object?>? continuation, object? state) | ||||
|             => Pool.Box(new WaitCallbackShim(continuation, state)); | ||||
|  | ||||
|         private void InvokeContinuation() => Continuation?.Invoke(State); | ||||
|  | ||||
|         public static readonly WaitCallback Invoke = state => Pool.UnboxAndReturn<WaitCallbackShim>(state).InvokeContinuation(); | ||||
|     } | ||||
|  | ||||
|     /// <summary>Provides the core logic for implementing a manual-reset <see cref="IValueTaskSource"/> or <see cref="IValueTaskSource{TResult}"/>.</summary> | ||||
|     /// <typeparam name="TResult"></typeparam> | ||||
|     [StructLayout(LayoutKind.Auto)] | ||||
|     public struct ManualResetValueTaskSourceCore<TResult> | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// The callback to invoke when the operation completes if <see cref="OnCompleted"/> was called before the operation completed, | ||||
|         /// or <see cref="ManualResetValueTaskSourceCoreShared.s_sentinel"/> if the operation completed before a callback was supplied, | ||||
|         /// or null if a callback hasn't yet been provided and the operation hasn't yet completed. | ||||
|         /// </summary> | ||||
|         private Action<object?>? _continuation; | ||||
|         /// <summary>State to pass to <see cref="_continuation"/>.</summary> | ||||
|         private object? _continuationState; | ||||
|         /// <summary><see cref="ExecutionContext"/> to flow to the callback, or null if no flowing is required.</summary> | ||||
|         private ExecutionContext? _executionContext; | ||||
|         /// <summary> | ||||
|         /// A "captured" <see cref="SynchronizationContext"/> or <see cref="TaskScheduler"/> with which to invoke the callback, | ||||
|         /// or null if no special context is required. | ||||
|         /// </summary> | ||||
|         private object? _capturedContext; | ||||
|         /// <summary>Whether the current operation has completed.</summary> | ||||
|         private bool _completed; | ||||
|         /// <summary>The result with which the operation succeeded, or the default value if it hasn't yet completed or failed.</summary> | ||||
|         /* [AllowNull, MaybeNull] */ | ||||
|         private TResult _result; | ||||
|         /// <summary>The exception with which the operation failed, or null if it hasn't yet completed or completed successfully.</summary> | ||||
|         private ExceptionDispatchInfo? _error; | ||||
|         /// <summary>The current version of this value, used to help prevent misuse.</summary> | ||||
|         private short _version; | ||||
|  | ||||
|         /// <summary>Gets or sets whether to force continuations to run asynchronously.</summary> | ||||
|         /// <remarks>Continuations may run asynchronously if this is false, but they'll never run synchronously if this is true.</remarks> | ||||
|         public bool RunContinuationsAsynchronously { get; set; } | ||||
|  | ||||
|         /// <summary>Resets to prepare for the next operation.</summary> | ||||
|         public void Reset() | ||||
|         { | ||||
|             // Reset/update state for the next use/await of this instance. | ||||
|             _version++; | ||||
|             _completed = false; | ||||
|             _result = default!; // TODO-NULLABLE: Remove ! when nullable attributes are respected | ||||
|             _error = null; | ||||
|             _executionContext = null; | ||||
|             _capturedContext = null; | ||||
|             _continuation = null; | ||||
|             _continuationState = null; | ||||
|         } | ||||
|  | ||||
|         /// <summary>Completes with a successful result.</summary> | ||||
|         /// <param name="result">The result.</param> | ||||
|         public void SetResult(TResult result) | ||||
|         { | ||||
|             _result = result; | ||||
|             SignalCompletion(); | ||||
|         } | ||||
|  | ||||
|         /// <summary>Complets with an error.</summary> | ||||
|         /// <param name="error"></param> | ||||
|         public void SetException(Exception error) | ||||
|         { | ||||
|             _error = ExceptionDispatchInfo.Capture(error); | ||||
|             SignalCompletion(); | ||||
|         } | ||||
|  | ||||
|         /// <summary>Gets the operation version.</summary> | ||||
|         public short Version => _version; | ||||
|  | ||||
|         /// <summary>Gets the status of the operation.</summary> | ||||
|         /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> | ||||
|         public ValueTaskSourceStatus GetStatus(short token) | ||||
|         { | ||||
|             ValidateToken(token); | ||||
|             return | ||||
|                 _continuation == null || !_completed ? ValueTaskSourceStatus.Pending : | ||||
|                 _error == null ? ValueTaskSourceStatus.Succeeded : | ||||
|                 _error.SourceException is OperationCanceledException ? ValueTaskSourceStatus.Canceled : | ||||
|                 ValueTaskSourceStatus.Faulted; | ||||
|         } | ||||
|  | ||||
|         /// <summary>Gets the result of the operation.</summary> | ||||
|         /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> | ||||
|         // [StackTraceHidden] | ||||
|         public TResult GetResult(short token) | ||||
|         { | ||||
|             ValidateToken(token); | ||||
|             if (!_completed) | ||||
|             { | ||||
|                 ThrowHelper.ThrowInvalidOperationException(); | ||||
|             } | ||||
|  | ||||
|             _error?.Throw(); | ||||
|             return _result; | ||||
|         } | ||||
|  | ||||
|         /// <summary>Schedules the continuation action for this operation.</summary> | ||||
|         /// <param name="continuation">The continuation to invoke when the operation has completed.</param> | ||||
|         /// <param name="state">The state object to pass to <paramref name="continuation"/> when it's invoked.</param> | ||||
|         /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> | ||||
|         /// <param name="flags">The flags describing the behavior of the continuation.</param> | ||||
|         public void OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) | ||||
|         { | ||||
|             if (continuation == null) ThrowHelper.ThrowArgumentNullException(nameof(continuation)); | ||||
|             ValidateToken(token); | ||||
|  | ||||
|             if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) != 0) | ||||
|             { | ||||
|                 _executionContext = ExecutionContext.Capture(); | ||||
|             } | ||||
|  | ||||
|             if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) != 0) | ||||
|             { | ||||
|                 SynchronizationContext? sc = SynchronizationContext.Current; | ||||
|                 if (sc != null && sc.GetType() != typeof(SynchronizationContext)) | ||||
|                 { | ||||
|                     _capturedContext = sc; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     TaskScheduler ts = TaskScheduler.Current; | ||||
|                     if (ts != TaskScheduler.Default) | ||||
|                     { | ||||
|                         _capturedContext = ts; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // We need to set the continuation state before we swap in the delegate, so that | ||||
|             // if there's a race between this and SetResult/Exception and SetResult/Exception | ||||
|             // sees the _continuation as non-null, it'll be able to invoke it with the state | ||||
|             // stored here.  However, this also means that if this is used incorrectly (e.g. | ||||
|             // awaited twice concurrently), _continuationState might get erroneously overwritten. | ||||
|             // To minimize the chances of that, we check preemptively whether _continuation | ||||
|             // is already set to something other than the completion sentinel. | ||||
|  | ||||
|             object? oldContinuation = _continuation; | ||||
|             if (oldContinuation == null) | ||||
|             { | ||||
|                 _continuationState = state; | ||||
|                 oldContinuation = Interlocked.CompareExchange(ref _continuation, continuation, null); | ||||
|             } | ||||
|  | ||||
|             if (oldContinuation != null) | ||||
|             { | ||||
|                 // Operation already completed, so we need to queue the supplied callback. | ||||
|                 if (!ReferenceEquals(oldContinuation, ManualResetValueTaskSourceCoreShared.s_sentinel)) | ||||
|                 { | ||||
|                     ThrowHelper.ThrowInvalidOperationException(); | ||||
|                 } | ||||
|  | ||||
|                 switch (_capturedContext) | ||||
|                 { | ||||
|                     case null: | ||||
|                         if (_executionContext != null) | ||||
|                         { | ||||
|                             ThreadPool.QueueUserWorkItem(WaitCallbackShim.Invoke, WaitCallbackShim.Create(continuation, state)); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
| #if NETSTANDARD1_3 | ||||
|                             ThreadPool.QueueUserWorkItem( | ||||
| #else | ||||
|                             ThreadPool.UnsafeQueueUserWorkItem( | ||||
| #endif | ||||
|                                 WaitCallbackShim.Invoke, WaitCallbackShim.Create(continuation, state)); | ||||
|                         } | ||||
|                         break; | ||||
|  | ||||
|                     case SynchronizationContext sc: | ||||
|                         sc.Post(s => | ||||
|                         { | ||||
|                             var tuple = (Tuple<Action<object?>, object?>)s!; | ||||
|                             tuple.Item1(tuple.Item2); | ||||
|                         }, Tuple.Create(continuation, state)); | ||||
|                         break; | ||||
|  | ||||
|                     case TaskScheduler ts: | ||||
|                         Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts); | ||||
|                         break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary>Ensures that the specified token matches the current version.</summary> | ||||
|         /// <param name="token">The token supplied by <see cref="ValueTask"/>.</param> | ||||
|         private void ValidateToken(short token) | ||||
|         { | ||||
|             if (token != _version) | ||||
|             { | ||||
|                 ThrowHelper.ThrowInvalidOperationException(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary>Signals that the operation has completed.  Invoked after the result or error has been set.</summary> | ||||
|         private void SignalCompletion() | ||||
|         { | ||||
|             if (_completed) | ||||
|             { | ||||
|                 ThrowHelper.ThrowInvalidOperationException(); | ||||
|             } | ||||
|             _completed = true; | ||||
|  | ||||
|             if (_continuation != null || Interlocked.CompareExchange(ref _continuation, ManualResetValueTaskSourceCoreShared.s_sentinel, null) != null) | ||||
|             { | ||||
|                 if (_executionContext != null) | ||||
|                 { | ||||
|                     ExecutionContext.Run( | ||||
|                         _executionContext, | ||||
|                         s_UnboxAndInvokeContextCallback, | ||||
|                         Pool.Box<ManualResetValueTaskSourceCore<TResult>>(this)); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     InvokeContinuation(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         static readonly ContextCallback s_UnboxAndInvokeContextCallback = state => Pool.UnboxAndReturn<ManualResetValueTaskSourceCore<TResult>>(state).InvokeContinuation(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Invokes the continuation with the appropriate captured context / scheduler. | ||||
|         /// This assumes that if <see cref="_executionContext"/> is not null we're already | ||||
|         /// running within that <see cref="ExecutionContext"/>. | ||||
|         /// </summary> | ||||
|         private void InvokeContinuation() | ||||
|         { | ||||
|             Debug.Assert(_continuation != null); | ||||
|  | ||||
|             switch (_capturedContext) | ||||
|             { | ||||
|                 case null: | ||||
|                     if (RunContinuationsAsynchronously) | ||||
|                     { | ||||
|                         if (_executionContext != null) | ||||
|                         { | ||||
|                             ThreadPool.QueueUserWorkItem(WaitCallbackShim.Invoke, WaitCallbackShim.Create(_continuation, _continuationState)); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
| #if NETSTANDARD1_3 | ||||
|                             ThreadPool.QueueUserWorkItem( | ||||
| #else | ||||
|                             ThreadPool.UnsafeQueueUserWorkItem( | ||||
| #endif | ||||
|                                 WaitCallbackShim.Invoke, WaitCallbackShim.Create(_continuation, _continuationState)); | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         _continuation!(_continuationState); | ||||
|                     } | ||||
|                     break; | ||||
|  | ||||
|                 case SynchronizationContext sc: | ||||
|                     sc.Post(s => | ||||
|                     { | ||||
|                         var state = (Tuple<Action<object?>, object?>)s!; | ||||
|                         state.Item1(state.Item2); | ||||
|                     }, Tuple.Create(_continuation, _continuationState)); | ||||
|                     break; | ||||
|  | ||||
|                 case TaskScheduler ts: | ||||
|                     Task.Factory.StartNew(_continuation, _continuationState, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts); | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal static class ManualResetValueTaskSourceCoreShared // separated out of generic to avoid unnecessary duplication | ||||
|     { | ||||
|         internal static readonly Action<object?> s_sentinel = CompletionSentinel; | ||||
|         private static void CompletionSentinel(object? _) // named method to aid debugging | ||||
|         { | ||||
|             Debug.Fail("The sentinel delegate should never be invoked."); | ||||
|             ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| #endif | ||||
| @@ -0,0 +1,9 @@ | ||||
| namespace PooledAwait.Internal | ||||
| { | ||||
|     internal readonly struct Nothing // to express ValueTask via PooledState<Nothing> | ||||
|     { | ||||
|         public override string ToString() => nameof(Nothing); | ||||
|         public override int GetHashCode() => 0; | ||||
|         public override bool Equals(object? obj) => obj is Nothing; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,124 @@ | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Threading.Tasks.Sources; | ||||
|  | ||||
| namespace PooledAwait.Internal | ||||
| { | ||||
|     internal sealed class PooledState<T> : IValueTaskSource<T>, IValueTaskSource | ||||
|     { | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static PooledState<T> Create(out short token) | ||||
|         { | ||||
|             var obj = Pool<PooledState<T>>.TryGet() ?? new PooledState<T>(); | ||||
|             token = obj._source.Version; | ||||
|             return obj; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private PooledState() => Counters.PooledStateAllocated.Increment(); | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal bool IsValid(short token) => _source.Version == token; | ||||
|  | ||||
|         private ManualResetValueTaskSourceCore<T> _source; // needs to be mutable | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public T GetResult(short token) | ||||
|         { | ||||
|             // we only support getting the result once; doing this recycles the source and advances the token | ||||
|  | ||||
|             lock (SyncLock) // we need to be really paranoid about cross-threading over changing the token | ||||
|             { | ||||
|                 var status = _source.GetStatus(token); // do this *outside* the try/finally | ||||
|                 try // so that we don't increment the counter if someone asks for the wrong value | ||||
|                 { | ||||
|                     switch (status) | ||||
|                     { | ||||
|                         case ValueTaskSourceStatus.Canceled: | ||||
|                             ThrowHelper.ThrowTaskCanceledException(); | ||||
|                             break; | ||||
|                         case ValueTaskSourceStatus.Pending: | ||||
|                             Monitor.Wait(SyncLock); | ||||
|                             break; | ||||
|                     } | ||||
|                     return _source.GetResult(token); | ||||
|                 } | ||||
|                 finally | ||||
|                 { | ||||
|                     _source.Reset(); | ||||
|                     if (_source.Version != TaskUtils.InitialTaskSourceVersion) | ||||
|                     { | ||||
|                         Pool<PooledState<T>>.TryPut(this); | ||||
|                         Counters.PooledStateRecycled.Increment(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         void IValueTaskSource.GetResult(short token) => GetResult(token); | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.NoInlining)] | ||||
|         private void SignalResult(short token) | ||||
|         { | ||||
|             lock (SyncLock) | ||||
|             { | ||||
|                 if (token == _source.Version && _source.GetStatus(token) != ValueTaskSourceStatus.Pending) | ||||
|                 { | ||||
|                     Monitor.Pulse(SyncLock); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public ValueTaskSourceStatus GetStatus(short token) => _source.GetStatus(token); | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) | ||||
|             => _source.OnCompleted(continuation, state, token, flags); | ||||
|  | ||||
|         private object SyncLock | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => this; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetException(Exception error, short token) | ||||
|         { | ||||
|             if (token == _source.Version) | ||||
|             { | ||||
|  | ||||
|                 switch (_source.GetStatus(token)) | ||||
|                 { | ||||
|                     case ValueTaskSourceStatus.Pending: | ||||
|                         _source.SetException(error); | ||||
|                         // only need to signal if SetException didn't inline a handler | ||||
|                         if (token == _source.Version) SignalResult(token); | ||||
|                         return true; | ||||
|                 } | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetResult(T result, short token) | ||||
|         { | ||||
|             if (token == _source.Version) | ||||
|             { | ||||
|                 switch (_source.GetStatus(token)) | ||||
|                 { | ||||
|                     case ValueTaskSourceStatus.Pending: | ||||
|                         _source.SetResult(result); | ||||
|                         // only need to signal if SetResult didn't inline a handler | ||||
|                         if (token == _source.Version) SignalResult(token); | ||||
|                         return true; | ||||
|                 } | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetCanceled(short token) | ||||
|             => TrySetException(TaskUtils.SharedTaskCanceledException, token); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,113 @@ | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait.Internal | ||||
| { | ||||
|     internal sealed class StateMachineBox<TStateMachine> | ||||
| #if PLAT_THREADPOOLWORKITEM | ||||
|         : IThreadPoolWorkItem | ||||
| #endif | ||||
|             where TStateMachine : IAsyncStateMachine | ||||
|     { | ||||
|         private readonly Action _execute; | ||||
|         private TStateMachine _stateMachine; | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private StateMachineBox() | ||||
|         { | ||||
|             _stateMachine = default!; | ||||
|             _execute = Execute; | ||||
|             Counters.StateMachineBoxAllocated.Increment(); | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private static StateMachineBox<TStateMachine> Create(ref TStateMachine stateMachine) | ||||
|         { | ||||
|             var box = Pool<StateMachineBox<TStateMachine>>.TryGet() ?? new StateMachineBox<TStateMachine>(); | ||||
|             box._stateMachine = stateMachine; | ||||
|             return box; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static void AwaitOnCompleted<TAwaiter>( | ||||
|             ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||||
|             where TAwaiter : INotifyCompletion | ||||
|         { | ||||
|             var box = Create(ref stateMachine); | ||||
|             if (typeof(TAwaiter) == typeof(YieldAwaitable.YieldAwaiter)) | ||||
|             { | ||||
|                 Yield(box, true); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 awaiter.OnCompleted(box._execute); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static void AwaitUnsafeOnCompleted<TAwaiter>( | ||||
|             ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||||
|             where TAwaiter : ICriticalNotifyCompletion | ||||
|         { | ||||
|             var box = Create(ref stateMachine); | ||||
|             if (typeof(TAwaiter) == typeof(YieldAwaitable.YieldAwaiter)) | ||||
|             { | ||||
|                 Yield(box, false); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 awaiter.UnsafeOnCompleted(box._execute); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private static void Yield(StateMachineBox<TStateMachine> box, bool flowContext) | ||||
|         { | ||||
|             // heavily inspired by YieldAwaitable.QueueContinuation | ||||
|  | ||||
|             var syncContext = SynchronizationContext.Current; | ||||
|             if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) | ||||
|             { | ||||
|                 syncContext.Post(s_SendOrPostCallback, box); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 var taskScheduler = TaskScheduler.Current; | ||||
|                 if (!ReferenceEquals(taskScheduler, TaskScheduler.Default)) | ||||
|                 { | ||||
|                     Task.Factory.StartNew(box._execute, default, TaskCreationOptions.PreferFairness, taskScheduler); | ||||
|                 } | ||||
|                 else if (flowContext) | ||||
|                 { | ||||
|                     ThreadPool.QueueUserWorkItem(s_WaitCallback, box); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
| #if PLAT_THREADPOOLWORKITEM | ||||
|                     ThreadPool.UnsafeQueueUserWorkItem(box, false); | ||||
| #elif NETSTANDARD1_3 | ||||
|                     ThreadPool.QueueUserWorkItem(s_WaitCallback, box); | ||||
| #else | ||||
|                     ThreadPool.UnsafeQueueUserWorkItem(s_WaitCallback, box); | ||||
| #endif | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         static readonly SendOrPostCallback s_SendOrPostCallback = state => ((StateMachineBox<TStateMachine>?)state!)?.Execute(); | ||||
|         static readonly WaitCallback s_WaitCallback = state => ((StateMachineBox<TStateMachine>?)state)?.Execute(); | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void Execute() | ||||
|         { | ||||
|             // extract the state | ||||
|             var tmp = _stateMachine; | ||||
|  | ||||
|             // recycle the instance | ||||
|             _stateMachine = default!; | ||||
|             Pool<StateMachineBox<TStateMachine>>.TryPut(this); | ||||
|             Counters.StateMachineBoxRecycled.Increment(); | ||||
|  | ||||
|             // progress the state machine | ||||
|             tmp.MoveNext(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,171 @@ | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Threading.Tasks.Sources; | ||||
|  | ||||
| namespace PooledAwait.Internal | ||||
| { | ||||
|     // NET45 lacks some useful Task APIs; shim over them | ||||
|     internal static class TaskUtils | ||||
|     { | ||||
|         internal static readonly short InitialTaskSourceVersion = new ManualResetValueTaskSourceCore<Nothing>().Version; | ||||
|  | ||||
|         public static readonly TaskCanceledException SharedTaskCanceledException = new TaskCanceledException(); | ||||
| #if NET45 | ||||
|         public static readonly Task CompletedTask = TaskFactory<Nothing>.FromResult(default); | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static Task<T> FromException<T>(Exception exception) | ||||
|         { | ||||
|             var source = ValueTaskCompletionSource<T>.Create(); | ||||
|             source.TrySetException(exception); | ||||
|             return source.Task; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static Task FromException(Exception exception) => FromException<bool>(exception); | ||||
| #else | ||||
|         public static readonly Task CompletedTask = Task.CompletedTask; | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static Task<T> FromException<T>(Exception exception) => Task.FromException<T>(exception); | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static Task FromException(Exception exception) => Task.FromException(exception); | ||||
| #endif | ||||
|  | ||||
|  | ||||
|         internal static class TaskFactory<TResult> | ||||
|         { | ||||
|             // draws from AsyncMethodBuilder, but less boxing | ||||
|  | ||||
|             public static readonly Task<TResult> Canceled = CreateCanceled(); | ||||
|  | ||||
|             static Task<TResult> CreateCanceled() | ||||
|             { | ||||
|                 var source = ValueTaskCompletionSource<TResult>.Create(); | ||||
|                 source.TrySetCanceled(); | ||||
|                 return source.Task; | ||||
|             } | ||||
|  | ||||
|             private static readonly TaskCache<TResult> _cache = (TaskCache<TResult>)CreateCacheForType(); | ||||
|  | ||||
|             private static object CreateCacheForType() | ||||
|             { | ||||
|                 if (typeof(TResult) == typeof(Nothing)) return new NothingTaskCache(); | ||||
|                 if (typeof(TResult) == typeof(int)) return new Int32TaskCache(); | ||||
|                 if (typeof(TResult) == typeof(int?)) return new NullableInt32TaskCache(); | ||||
|                 if (typeof(TResult) == typeof(bool)) return new BooleanTaskCache(); | ||||
|                 if (typeof(TResult) == typeof(bool?)) return new NullableBooleanTaskCache(); | ||||
|  | ||||
|                 Type underlyingType = Nullable.GetUnderlyingType(typeof(TResult)) ?? typeof(TResult); | ||||
|                 if (underlyingType == typeof(uint) | ||||
|                  || underlyingType == typeof(byte) | ||||
|                  || underlyingType == typeof(sbyte) | ||||
|                  || underlyingType == typeof(char) | ||||
|                  || underlyingType == typeof(decimal) | ||||
|                  || underlyingType == typeof(long) | ||||
|                  || underlyingType == typeof(ulong) | ||||
|                  || underlyingType == typeof(short) | ||||
|                  || underlyingType == typeof(ushort) | ||||
|                  || underlyingType == typeof(float) | ||||
|                  || underlyingType == typeof(double) | ||||
|                  || underlyingType == typeof(IntPtr) | ||||
|                  || underlyingType == typeof(UIntPtr) | ||||
|                     ) return new DefaultEquatableTaskCache<TResult>(); | ||||
|  | ||||
|                 if (typeof(TResult) == typeof(string)) return new StringTaskCache(); | ||||
| #if !NETSTANDARD1_3 | ||||
|                 if (!typeof(TResult).IsValueType) return new ObjectTaskCache<TResult>(); | ||||
| #endif | ||||
|                 return new TaskCache<TResult>(); | ||||
|             } | ||||
|  | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             public static Task<TResult> FromResult(TResult result) => _cache.FromResult(result); | ||||
|         } | ||||
|  | ||||
|         class TaskCache<TResult> | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             internal virtual Task<TResult> FromResult(TResult value) => Task.FromResult(value); | ||||
|         } | ||||
|         class NothingTaskCache : TaskCache<Nothing> | ||||
|         { | ||||
|             private static readonly Task<Nothing> s_Instance = Task.FromResult<Nothing>(default); | ||||
|             internal override Task<Nothing> FromResult(Nothing value) => s_Instance; | ||||
|         } | ||||
|         class DefaultEquatableTaskCache<TResult> : TaskCache<TResult> | ||||
|         { | ||||
|             private static readonly Task<TResult> s_Default = Task.FromResult<TResult>(default!); | ||||
|             private static readonly EqualityComparer<TResult> _comparer = EqualityComparer<TResult>.Default; | ||||
|             internal override Task<TResult> FromResult(TResult value) | ||||
|                 => _comparer.Equals(value, default!) ? s_Default : base.FromResult(value); | ||||
|         } | ||||
|         class ObjectTaskCache<TResult> : TaskCache<TResult> | ||||
|         { | ||||
|             private static readonly Task<TResult> s_Null = Task.FromResult<TResult>(default!); | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             internal override Task<TResult> FromResult(TResult value) | ||||
|                 => value == null ? s_Null : base.FromResult(value); | ||||
|         } | ||||
|         sealed class StringTaskCache : ObjectTaskCache<string> | ||||
|         { | ||||
|             private static readonly Task<string> s_Empty = Task.FromResult<string>(""); | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             internal override Task<string> FromResult(string value) | ||||
|                 => string.IsNullOrEmpty(value) ? s_Empty : base.FromResult(value); | ||||
|         } | ||||
|         sealed class BooleanTaskCache : TaskCache<bool> | ||||
|         { | ||||
|             static readonly Task<bool> s_True = Task.FromResult(true), s_False = Task.FromResult(false); | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             internal override Task<bool> FromResult(bool value) => value ? s_True : s_False; | ||||
|         } | ||||
|         sealed class NullableBooleanTaskCache : TaskCache<bool?> | ||||
|         { | ||||
|             static readonly Task<bool?> s_True = Task.FromResult((bool?)true), s_False = Task.FromResult((bool?)false), | ||||
|                 s_Null = Task.FromResult((bool?)null); | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             internal override Task<bool?> FromResult(bool? value) => | ||||
|                 value.HasValue ? (value.GetValueOrDefault() ? s_True : s_False) : s_Null; | ||||
|         } | ||||
|         sealed class Int32TaskCache : TaskCache<int> | ||||
|         { | ||||
|             const int MIN_INC = -1, MAX_EXC = 11; | ||||
|             static readonly Task<int>[] s_Known = CreateKnown(); | ||||
|             static Task<int>[] CreateKnown() | ||||
|             { | ||||
|                 var arr = new Task<int>[MAX_EXC - MIN_INC]; | ||||
|                 for (int i = 0; i < arr.Length; i++) | ||||
|                     arr[i] = Task.FromResult(i + MIN_INC); | ||||
|                 return arr; | ||||
|             } | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             internal override Task<int> FromResult(int value) | ||||
|                 => value >= MIN_INC && value < MAX_EXC ? s_Known[value - MIN_INC] : base.FromResult(value); | ||||
|         } | ||||
|         sealed class NullableInt32TaskCache : TaskCache<int?> | ||||
|         { | ||||
|             const int MIN_INC = -1, MAX_EXC = 11; | ||||
|             static readonly Task<int?>[] s_Known = CreateKnown(); | ||||
|             static readonly Task<int?> s_Null = Task.FromResult((int?)null); | ||||
|             static Task<int?>[] CreateKnown() | ||||
|             { | ||||
|                 var arr = new Task<int?>[MAX_EXC - MIN_INC]; | ||||
|                 for (int i = 0; i < arr.Length; i++) | ||||
|                     arr[i] = Task.FromResult((int?)(i + MIN_INC)); | ||||
|                 return arr; | ||||
|             } | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             internal override Task<int?> FromResult(int? nullable) | ||||
|             { | ||||
|                 if (nullable.HasValue) | ||||
|                 { | ||||
|                     int value = nullable.GetValueOrDefault(); | ||||
|                     return value >= MIN_INC && value < MAX_EXC ? s_Known[value - MIN_INC] : base.FromResult(nullable); | ||||
|                 } | ||||
|                 return s_Null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,30 @@ | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait.Internal | ||||
| { | ||||
|     internal static class ThrowHelper | ||||
|     { | ||||
|         [MethodImpl(MethodImplOptions.NoInlining)] | ||||
|         internal static void ThrowInvalidOperationException(string? message = null) | ||||
|         { | ||||
|             if (string.IsNullOrWhiteSpace(message)) throw new InvalidOperationException(); | ||||
|             else throw new InvalidOperationException(message); | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.NoInlining)] | ||||
|         internal static T ThrowInvalidOperationException<T>(string? message = null) | ||||
|         { | ||||
|             ThrowInvalidOperationException(message); | ||||
|             return default!; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.NoInlining)] | ||||
|         internal static T ThrowNotSupportedException<T>() => throw new NotSupportedException(); | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.NoInlining)] | ||||
|         internal static void ThrowArgumentNullException(string paramName) => throw new ArgumentNullException(paramName); | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.NoInlining)] | ||||
|         internal static void ThrowTaskCanceledException() => throw new TaskCanceledException(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,127 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Like a ValueTaskCompletionSource, but the actual task will only become allocated | ||||
|     /// if the .Task is consumed; this is useful for APIs where the consumer *may* consume a task | ||||
|     /// </summary> | ||||
|     public readonly struct LazyTaskCompletionSource : IDisposable | ||||
|     { | ||||
|  | ||||
|         private static LazyTaskCompletionSource _completed, _canceled; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// A global LazyTaskCompletionSource that represents a completed operation | ||||
|         /// </summary> | ||||
|         public static LazyTaskCompletionSource CompletedTask | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _completed.IsValid ? _completed : _completed = new LazyTaskCompletionSource(LazyTaskState<Nothing>.CreateConstant(default)); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// A global LazyTaskCompletionSource that represents a cancelled operation | ||||
|         /// </summary> | ||||
|         public static LazyTaskCompletionSource CanceledTask | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _canceled.IsValid ? _canceled : _canceled = new LazyTaskCompletionSource(LazyTaskState<Nothing>.CreateCanceled()); | ||||
|         } | ||||
|  | ||||
|         /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||
|         public override bool Equals(object? obj) => obj is LazyTaskCompletionSource ltcs && _state == ltcs._state && _token == ltcs._token; | ||||
|         /// <summary><see cref="Object.GetHashCode"/></summary> | ||||
|         public override int GetHashCode() => (_state == null ? 0 : _state.GetHashCode()) ^ _token; | ||||
|         /// <summary><see cref="Object.ToString"/></summary> | ||||
|         public override string ToString() => nameof(LazyTaskCompletionSource); | ||||
|  | ||||
|         private readonly LazyTaskState<Nothing> _state; | ||||
|         private readonly short _token; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the task associated with this instance | ||||
|         /// </summary> | ||||
|         public Task Task => _state?.GetTask(_token) ?? ThrowHelper.ThrowInvalidOperationException<Task>(); | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private LazyTaskCompletionSource(LazyTaskState<Nothing> state) | ||||
|         { | ||||
|             _state = state; | ||||
|             _token = state.Version; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Create a new instance; this instance should be disposed when it is known to be unwanted | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static LazyTaskCompletionSource Create() | ||||
|             => new LazyTaskCompletionSource(LazyTaskState<Nothing>.Create()); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetResult() => _state?.TrySetResult(_token, default) == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetCanceled(CancellationToken cancellationToken = default) => _state?.TrySetCanceled(_token, cancellationToken) == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetResult() | ||||
|         { | ||||
|             if (!TrySetResult()) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetCanceled(CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetException(Exception exception) => _state?.TrySetException(_token, exception) == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetException(Exception exception) | ||||
|         { | ||||
|             if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Release all resources associated with this operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void Dispose() => _state?.Recycle(_token); | ||||
|  | ||||
|         internal bool IsValid => _state?.IsValid(_token) == true; | ||||
|         internal bool HasSource => _state?.HasSource == true; | ||||
|         internal bool HasTask => _state?.HasTask == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Indicates whether this is an invalid default instance | ||||
|         /// </summary> | ||||
|         public bool IsNull | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _state == null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,126 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Like a ValueTaskCompletionSource<typeparamref name="T"/>, but the actual task will only become allocated | ||||
|     /// if the .Task is consumed; this is useful for APIs where the consumer *may* consume a task | ||||
|     /// </summary> | ||||
|     public readonly struct LazyTaskCompletionSource<T> : IDisposable | ||||
|     { | ||||
|         /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||
|         public override bool Equals(object? obj) => obj is LazyTaskCompletionSource<T> ltcs && _state == ltcs._state && _token == ltcs._token; | ||||
|         /// <summary><see cref="Object.GetHashCode"/></summary> | ||||
|         public override int GetHashCode() => (_state == null ? 0 : _state.GetHashCode()) ^ _token; | ||||
|         /// <summary><see cref="Object.ToString"/></summary> | ||||
|         public override string ToString() => nameof(LazyTaskCompletionSource); | ||||
|  | ||||
|         private readonly LazyTaskState<T> _state; | ||||
|         private readonly short _token; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the task associated with this instance | ||||
|         /// </summary> | ||||
|         public Task<T> Task => (Task<T>)(_state?.GetTask(_token) ?? ThrowHelper.ThrowInvalidOperationException<Task<T>>()); | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private LazyTaskCompletionSource(LazyTaskState<T> state) | ||||
|         { | ||||
|             _state = state; | ||||
|             _token = state.Version; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Create a new instance; this instance should be disposed when it is known to be unwanted | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static LazyTaskCompletionSource<T> Create() | ||||
|             => new LazyTaskCompletionSource<T>(LazyTaskState<T>.Create()); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Create a new instance; this instance will never by recycled | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static LazyTaskCompletionSource<T> CreateConstant(T value) | ||||
|             => new LazyTaskCompletionSource<T>(LazyTaskState<T>.CreateConstant(value)); | ||||
|  | ||||
|  | ||||
|         private static LazyTaskCompletionSource<T> _canceled; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// A global LazyTaskCompletionSource that represents a cancelled operation | ||||
|         /// </summary> | ||||
|         public static LazyTaskCompletionSource<T> CanceledTask | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _canceled.IsValid ? _canceled : _canceled = new LazyTaskCompletionSource<T>(LazyTaskState<T>.CreateCanceled()); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetResult(T result) => _state?.TrySetResult(_token, result) == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetResult(T result) | ||||
|         { | ||||
|             if (!TrySetResult(result)) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetCanceled(CancellationToken cancellationToken = default) | ||||
|             => _state?.TrySetCanceled(_token, cancellationToken) == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetCanceled(CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetException(Exception exception) => _state?.TrySetException(_token, exception) == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetException(Exception exception) | ||||
|         { | ||||
|             if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Release all resources associated with this operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void Dispose() => _state?.Recycle(_token); | ||||
|  | ||||
|         internal bool IsValid => _state?.IsValid(_token) == true; | ||||
|         internal bool HasSource => _state?.HasSource == true; | ||||
|         internal bool HasTask => _state?.HasTask == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Indicates whether this is an invalid default instance | ||||
|         /// </summary> | ||||
|         public bool IsNull | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _state == null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,73 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.ComponentModel; | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| #pragma warning disable CS1591 | ||||
|  | ||||
| namespace PooledAwait.MethodBuilders | ||||
| { | ||||
|     /// <summary> | ||||
|     /// This type is not intended for direct usage | ||||
|     /// </summary> | ||||
|     [Browsable(false)] | ||||
|     [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|     public struct FireAndForgetMethodBuilder | ||||
|     { | ||||
|         public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>(); | ||||
|         public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>(); | ||||
|         public override string ToString() => nameof(FireAndForgetMethodBuilder); | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static FireAndForgetMethodBuilder Create() => default; | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment(); | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetResult() { } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetException(Exception exception) => FireAndForget.OnException(exception); | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         public FireAndForget Task | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => default; | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void AwaitOnCompleted<TAwaiter, TStateMachine>( | ||||
|             ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||||
|             where TAwaiter : INotifyCompletion | ||||
|             where TStateMachine : IAsyncStateMachine | ||||
|             => StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine); | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>( | ||||
|             ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||||
|             where TAwaiter : ICriticalNotifyCompletion | ||||
|             where TStateMachine : IAsyncStateMachine | ||||
|             => StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void Start<TStateMachine>(ref TStateMachine stateMachine) | ||||
|             where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,105 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.ComponentModel; | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| using SystemTask = System.Threading.Tasks.Task; | ||||
|  | ||||
| #pragma warning disable CS1591 | ||||
|  | ||||
| namespace PooledAwait.MethodBuilders | ||||
| { | ||||
|     /// <summary> | ||||
|     /// This type is not intended for direct usage | ||||
|     /// </summary> | ||||
|     [Browsable(false)] | ||||
|     [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|     public struct PooledTaskMethodBuilder | ||||
|     { | ||||
|         public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>(); | ||||
|         public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>(); | ||||
|         public override string ToString() => nameof(PooledTaskMethodBuilder); | ||||
|  | ||||
|         private ValueTaskCompletionSource<Nothing> _source; | ||||
|         private Exception _exception; | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static PooledTaskMethodBuilder Create() => default; | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment(); | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetResult() | ||||
|         { | ||||
|             _source.TrySetResult(default); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetException(Exception exception) | ||||
|         { | ||||
|             _source.TrySetException(exception); | ||||
|             _exception = exception; | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private void EnsureHasTask() | ||||
|         { | ||||
|             if (_source.IsNull) _source = ValueTaskCompletionSource<Nothing>.Create(); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         public PooledTask Task | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get | ||||
|             { | ||||
|                 SystemTask task; | ||||
|                 if (!_source.IsNull) task = _source.Task; | ||||
|                 else if (_exception is OperationCanceledException) task = TaskUtils.TaskFactory<Nothing>.Canceled; | ||||
|                 else if (_exception != null) task = TaskUtils.FromException(_exception); | ||||
|                 else task = TaskUtils.CompletedTask; | ||||
|                 return new PooledTask(task); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void AwaitOnCompleted<TAwaiter, TStateMachine>( | ||||
|             ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||||
|             where TAwaiter : INotifyCompletion | ||||
|             where TStateMachine : IAsyncStateMachine | ||||
|         { | ||||
|             EnsureHasTask(); | ||||
|             StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>( | ||||
|             ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||||
|             where TAwaiter : ICriticalNotifyCompletion | ||||
|             where TStateMachine : IAsyncStateMachine | ||||
|         { | ||||
|             EnsureHasTask(); | ||||
|             StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void Start<TStateMachine>(ref TStateMachine stateMachine) | ||||
|             where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,105 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.ComponentModel; | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| #pragma warning disable CS1591 | ||||
|  | ||||
| namespace PooledAwait.MethodBuilders | ||||
| { | ||||
|     /// <summary> | ||||
|     /// This type is not intended for direct usage | ||||
|     /// </summary> | ||||
|     [Browsable(false)] | ||||
|     [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|     public struct PooledTaskMethodBuilder<T> | ||||
|     { | ||||
|         public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>(); | ||||
|         public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>(); | ||||
|         public override string ToString() => nameof(PooledTaskMethodBuilder); | ||||
|  | ||||
|         private ValueTaskCompletionSource<T> _source; | ||||
|         private Exception _exception; | ||||
|         private T _result; | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static PooledTaskMethodBuilder<T> Create() => default; | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment(); | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetResult(T result) | ||||
|         { | ||||
|             _source.TrySetResult(result); | ||||
|             _result = result; | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetException(Exception exception) | ||||
|         { | ||||
|             _source.TrySetException(exception); | ||||
|             _exception = exception; | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         public PooledTask<T> Task | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get | ||||
|             { | ||||
|                 Task<T> task; | ||||
|                 if (!_source.IsNull) task = _source.Task; | ||||
|                 else if (_exception is OperationCanceledException) task = TaskUtils.TaskFactory<T>.Canceled; | ||||
|                 else if (_exception != null) task = TaskUtils.FromException<T>(_exception); | ||||
|                 else task = TaskUtils.TaskFactory<T>.FromResult(_result); | ||||
|                 return new PooledTask<T>(task); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private void EnsureHasTask() | ||||
|         { | ||||
|             if (_source.IsNull) _source = ValueTaskCompletionSource<T>.Create(); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void AwaitOnCompleted<TAwaiter, TStateMachine>( | ||||
|             ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||||
|             where TAwaiter : INotifyCompletion | ||||
|             where TStateMachine : IAsyncStateMachine | ||||
|         { | ||||
|             EnsureHasTask(); | ||||
|             StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>( | ||||
|             ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||||
|             where TAwaiter : ICriticalNotifyCompletion | ||||
|             where TStateMachine : IAsyncStateMachine | ||||
|         { | ||||
|             EnsureHasTask(); | ||||
|             StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void Start<TStateMachine>(ref TStateMachine stateMachine) | ||||
|             where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,94 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.ComponentModel; | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| #pragma warning disable CS1591 | ||||
|  | ||||
| namespace PooledAwait.MethodBuilders | ||||
| { | ||||
|     /// <summary> | ||||
|     /// This type is not intended for direct usage | ||||
|     /// </summary> | ||||
|     [Browsable(false)] | ||||
|     [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|     public struct PooledValueTaskMethodBuilder | ||||
|     { | ||||
|         public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>(); | ||||
|         public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>(); | ||||
|         public override string ToString() => nameof(PooledValueTaskMethodBuilder); | ||||
|  | ||||
|         private PooledValueTaskSource _source; | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static PooledValueTaskMethodBuilder Create() => default; | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment(); | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetResult() | ||||
|         { | ||||
|             _source.TrySetResult(); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetException(Exception exception) | ||||
|         { | ||||
|             EnsureHasTask(); | ||||
|             _source.TrySetException(exception); | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private void EnsureHasTask() | ||||
|         { | ||||
|             if (!_source.HasTask) _source = PooledValueTaskSource.Create(); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         public PooledValueTask Task | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source.PooledTask; | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void AwaitOnCompleted<TAwaiter, TStateMachine>( | ||||
|             ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||||
|             where TAwaiter : INotifyCompletion | ||||
|             where TStateMachine : IAsyncStateMachine | ||||
|         { | ||||
|             EnsureHasTask(); | ||||
|             StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>( | ||||
|             ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||||
|             where TAwaiter : ICriticalNotifyCompletion | ||||
|             where TStateMachine : IAsyncStateMachine | ||||
|         { | ||||
|             EnsureHasTask(); | ||||
|             StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void Start<TStateMachine>(ref TStateMachine stateMachine) | ||||
|             where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,95 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.ComponentModel; | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| #pragma warning disable CS1591 | ||||
|  | ||||
| namespace PooledAwait.MethodBuilders | ||||
| { | ||||
|     /// <summary> | ||||
|     /// This type is not intended for direct usage | ||||
|     /// </summary> | ||||
|     [Browsable(false)] | ||||
|     [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|     public struct PooledValueTaskMethodBuilder<T> | ||||
|     { | ||||
|         public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>(); | ||||
|         public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>(); | ||||
|         public override string ToString() => nameof(PooledValueTaskMethodBuilder); | ||||
|  | ||||
|         private PooledValueTaskSource<T> _source; | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static PooledValueTaskMethodBuilder<T> Create() => default; | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment(); | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetResult(T result) | ||||
|         { | ||||
|             if (_source.HasTask) _source.TrySetResult(result); | ||||
|             else _source = new PooledValueTaskSource<T>(result); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetException(Exception exception) | ||||
|         { | ||||
|             EnsureHasTask(); | ||||
|             _source.TrySetException(exception); | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private void EnsureHasTask() | ||||
|         { | ||||
|             if (!_source.HasTask) _source = PooledValueTaskSource<T>.Create(); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         public PooledValueTask<T> Task | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source.PooledTask; | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void AwaitOnCompleted<TAwaiter, TStateMachine>( | ||||
|             ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||||
|             where TAwaiter : INotifyCompletion | ||||
|             where TStateMachine : IAsyncStateMachine | ||||
|         { | ||||
|             EnsureHasTask(); | ||||
|             StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>( | ||||
|             ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||||
|             where TAwaiter : ICriticalNotifyCompletion | ||||
|             where TStateMachine : IAsyncStateMachine | ||||
|         { | ||||
|             EnsureHasTask(); | ||||
|             StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); | ||||
|         } | ||||
|  | ||||
|         [Browsable(false)] | ||||
|         [EditorBrowsable(EditorBrowsableState.Never)] | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void Start<TStateMachine>(ref TStateMachine stateMachine) | ||||
|             where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										73
									
								
								src/Admin/ThingsGateway.NewLife.X/PooledAwait/Pool.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/Admin/ThingsGateway.NewLife.X/PooledAwait/Pool.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Utility methods for boxing value types efficiently, in particular for | ||||
|     /// avoid boxes and capture contexts in callbacks | ||||
|     /// </summary> | ||||
|     public static class Pool | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets an instance from the pool if possible | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static T? TryRent<T>() where T : class | ||||
|             => Pool<T>.TryGet(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Puts an instance back into the pool | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static void Return<T>(T value) where T : class | ||||
|         { | ||||
|             if (value is IResettable reset) reset.Reset(); | ||||
|             Pool<T>.TryPut(value); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Wraps a value-type into a boxed instance, using an object pool; | ||||
|         /// consider using value-tuples in particular | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static object Box<T>(in T value) where T : struct | ||||
|             => ItemBox<T>.Create(in value); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Unwraps a value-type from a boxed instance and recycles | ||||
|         /// the instance, which should not be touched again | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static T UnboxAndReturn<T>(object obj) where T : struct | ||||
|             => ItemBox<T>.UnboxAndRecycle(obj); | ||||
|  | ||||
|         internal sealed class ItemBox<T> where T : struct | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             private ItemBox() => Counters.ItemBoxAllocated.Increment(); | ||||
|  | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             public static ItemBox<T> Create(in T value) | ||||
|             { | ||||
|                 var box = Pool<ItemBox<T>>.TryGet() ?? new ItemBox<T>(); | ||||
|                 box._value = value; | ||||
|                 return box; | ||||
|             } | ||||
|  | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             public static T UnboxAndRecycle(object obj) | ||||
|             { | ||||
|                 var box = (ItemBox<T>)obj; | ||||
|                 var value = box._value; | ||||
|                 box._value = default; | ||||
|                 Pool<ItemBox<T>>.TryPut(box); | ||||
|                 return value; | ||||
|             } | ||||
| #pragma warning disable IDE0044 // make field readonly? no, IDE, you're wrong | ||||
|             private T _value; | ||||
| #pragma warning restore IDE0044 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,21 @@ | ||||
| #if !NETSTANDARD1_3 | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Controls the number of elements to store in the pool | ||||
|     /// </summary> | ||||
|     [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] | ||||
|     public sealed class PoolSizeAttribute : Attribute | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// The number of elements to store in the pool | ||||
|         /// </summary> | ||||
|         public int Size { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Create a new PoolSizeAttribute instance | ||||
|         /// </summary> | ||||
|         public PoolSizeAttribute(int size) => Size = size; | ||||
|     } | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										37
									
								
								src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolT.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolT.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| using ThingsGateway.NewLife.Collections; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A general-purpose pool of object references; it is the caller's responsibility | ||||
|     /// to ensure that overlapped usage does not occur | ||||
|     /// </summary> | ||||
|     internal static class Pool<T> where T : class | ||||
|     { | ||||
|         private static ObjectPoolLock<T> pool = new(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets an instance from the pool if possible | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static T? TryGet() | ||||
|         { | ||||
|             return pool.Get(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Puts an instance back into the pool | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static void TryPut(T value) | ||||
|         { | ||||
|             if (value != null) | ||||
|             { | ||||
|                 pool.Return(value); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,29 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 	<Import Project="..\..\Foundation.props" /> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<IncludeAsyncInterfaces>false</IncludeAsyncInterfaces> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<PropertyGroup Condition="$(TargetFramework)=='net6.0'"> | ||||
| 		<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants> | ||||
| 	</PropertyGroup> | ||||
| 	<PropertyGroup Condition="$(TargetFramework)=='net8.0'"> | ||||
| 		<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<PropertyGroup Condition="$(TargetFramework)=='netstandard2.0'"> | ||||
| 		<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants> | ||||
| 		<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<PropertyGroup Condition="$(TargetFramework)=='net462'"> | ||||
| 		<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants> | ||||
| 		<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.0" Condition="$(IncludeAsyncInterfaces)=='true'" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| </Project> | ||||
							
								
								
									
										60
									
								
								src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTask.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTask.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A Task, but with a custom builder | ||||
|     /// </summary> | ||||
|     [AsyncMethodBuilder(typeof(MethodBuilders.PooledTaskMethodBuilder))] | ||||
|     public readonly struct PooledTask | ||||
|     { | ||||
|         /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||
|         public override bool Equals(object? obj) => obj is PooledTask pt && _task == pt._task; | ||||
|         /// <summary><see cref="Object.GetHashCode"/></summary> | ||||
|         public override int GetHashCode() => _task == null ? 0 : _task.GetHashCode(); | ||||
|         /// <summary><see cref="Object.ToString"/></summary> | ||||
|         public override string ToString() => nameof(PooledTask); | ||||
|  | ||||
|         private readonly Task _task; | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal PooledTask(Task task) => _task = task; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public Task AsTask() => _task ?? TaskUtils.CompletedTask; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|  | ||||
|         public static implicit operator Task(in PooledTask task) => task.AsTask(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the awaiter for the task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public TaskAwaiter GetAwaiter() => AsTask().GetAwaiter(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the configured awaiter for the task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) | ||||
|             => AsTask().ConfigureAwait(continueOnCapturedContext); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Indicates whether this is an invalid default instance | ||||
|         /// </summary> | ||||
|         public bool IsNull | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _task == null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										60
									
								
								src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTaskT.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTaskT.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A Task<typeparamref name="T"/>, but with a custom builder | ||||
|     /// </summary> | ||||
|     [AsyncMethodBuilder(typeof(MethodBuilders.PooledTaskMethodBuilder<>))] | ||||
|     public readonly struct PooledTask<T> | ||||
|     { | ||||
|         /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||
|         public override bool Equals(object? obj) => obj is PooledTask<T> pt && _task == pt._task; | ||||
|         /// <summary><see cref="Object.GetHashCode"/></summary> | ||||
|         public override int GetHashCode() => _task == null ? 0 : _task.GetHashCode(); | ||||
|         /// <summary><see cref="Object.ToString"/></summary> | ||||
|         public override string ToString() => nameof(PooledTask); | ||||
|  | ||||
|         private readonly Task<T>? _task; | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal PooledTask(Task<T> task) => _task = task; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public Task<T> AsTask() => _task ?? ThrowHelper.ThrowInvalidOperationException<Task<T>>(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|  | ||||
|         public static implicit operator Task<T>(in PooledTask<T> task) => task.AsTask(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the awaiter for the task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public TaskAwaiter<T> GetAwaiter() => AsTask().GetAwaiter(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the configured awaiter for the task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public ConfiguredTaskAwaitable<T> ConfigureAwait(bool continueOnCapturedContext) | ||||
|             => AsTask().ConfigureAwait(continueOnCapturedContext); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Indicates whether this is an invalid default instance | ||||
|         /// </summary> | ||||
|         public bool IsNull | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _task == null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,71 @@ | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Threading.Tasks.Sources; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A ValueTask with a custom source and builder | ||||
|     /// </summary> | ||||
|     [AsyncMethodBuilder(typeof(MethodBuilders.PooledValueTaskMethodBuilder))] | ||||
|     public readonly struct PooledValueTask | ||||
|     { | ||||
|         /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||
|         public override bool Equals(object? obj) => obj is PooledValueTask pvt && _source == pvt._source && _token == pvt._token; | ||||
|         /// <summary><see cref="Object.GetHashCode"/></summary> | ||||
|         public override int GetHashCode() => (_source == null ? 0 : _source.GetHashCode()) ^ _token; | ||||
|         /// <summary><see cref="Object.ToString"/></summary> | ||||
|         public override string ToString() => nameof(PooledValueTask); | ||||
|  | ||||
|         private readonly IValueTaskSource _source; | ||||
|         private readonly short _token; | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal PooledValueTask(IValueTaskSource source, short token) | ||||
|         { | ||||
|             _source = source; | ||||
|             _token = token; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a value-task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public ValueTask AsValueTask() => _source == null ? default : new ValueTask(_source, _token); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a value-task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static implicit operator ValueTask(in PooledValueTask task) => task.AsValueTask(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the awaiter for the task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| #pragma warning disable CA2012 // 正确使用 ValueTask | ||||
|         public ValueTaskAwaiter GetAwaiter() => AsValueTask().GetAwaiter(); | ||||
| #pragma warning restore CA2012 // 正确使用 ValueTask | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the configured awaiter for the task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) | ||||
|             => AsValueTask().ConfigureAwait(continueOnCapturedContext); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Rents a task-source that will be recycled when the task is awaited | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal static PooledValueTaskSource CreateSource() => PooledValueTaskSource.Create(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Indicates whether this is an invalid default instance | ||||
|         /// </summary> | ||||
|         public bool IsNull | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source == null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,126 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A task-source that automatically recycles when the task is awaited | ||||
|     /// </summary> | ||||
|     public readonly struct PooledValueTaskSource | ||||
|     { | ||||
|         /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||
|         public override bool Equals(object? obj) => obj is PooledValueTaskSource pvt && _source == pvt._source && _token == pvt._token; | ||||
|         /// <summary><see cref="Object.GetHashCode"/></summary> | ||||
|         public override int GetHashCode() => (_source == null ? 0 : _source.GetHashCode()) ^ _token; | ||||
|         /// <summary><see cref="Object.ToString"/></summary> | ||||
|         public override string ToString() => nameof(PooledValueTaskSource); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the task that corresponds to this instance; it can only be awaited once | ||||
|         /// </summary> | ||||
|         public ValueTask Task | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source == null ? default : new ValueTask(_source, _token); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Indicates whether this instance is well-defined against a value task instance | ||||
|         /// </summary> | ||||
|         public bool HasTask | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source != null; | ||||
|         } | ||||
|  | ||||
|         internal PooledValueTask PooledTask | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => new PooledValueTask(_source, _token); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Rents a task-source that will be recycled when the task is awaited | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static PooledValueTaskSource Create() | ||||
|         { | ||||
|             var source = PooledState<Nothing>.Create(out var token); | ||||
|             return new PooledValueTaskSource(source, token); | ||||
|         } | ||||
|  | ||||
|         private readonly PooledState<Nothing> _source; | ||||
|         private readonly short _token; | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal PooledValueTaskSource(PooledState<Nothing> source, short token) | ||||
|         { | ||||
|             _source = source; | ||||
|             _token = token; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Test whether the source is valid | ||||
|         /// </summary> | ||||
|         public bool IsValid | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source?.IsValid(_token) == true; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetResult() => _source?.TrySetResult(default, _token) == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetResult() | ||||
|         { | ||||
|             if (!TrySetResult()) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetException(Exception error) => _source?.TrySetException(error, _token) == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetException(Exception exception) | ||||
|         { | ||||
|             if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetCanceled() => _source?.TrySetCanceled(_token) == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetCanceled() | ||||
|         { | ||||
|             if (!TrySetCanceled()) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Indicates whether this is an invalid default instance | ||||
|         /// </summary> | ||||
|         public bool IsNull | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source == null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,141 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A task-source that automatically recycles when the task is awaited | ||||
|     /// </summary> | ||||
|     public readonly struct PooledValueTaskSource<T> | ||||
|     { | ||||
|         /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||
|         public override bool Equals(object? obj) => obj is PooledValueTaskSource<T> pvt && _token == pvt._token && | ||||
|             (_source != null ? _source == pvt._source : (pvt._source == null && EqualityComparer<T>.Default.Equals(_value, pvt._value))); | ||||
|         /// <summary><see cref="Object.GetHashCode"/></summary> | ||||
|         public override int GetHashCode() => (_source == null ? EqualityComparer<T>.Default.GetHashCode(_value!) : _source.GetHashCode()) ^ _token; | ||||
|         /// <summary><see cref="Object.ToString"/></summary> | ||||
|         public override string ToString() => nameof(PooledValueTaskSource); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the task that corresponds to this instance; it can only be awaited once | ||||
|         /// </summary> | ||||
|         public ValueTask<T> Task | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source == null ? new ValueTask<T>(_value) : new ValueTask<T>(_source, _token); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Indicates whether this instance is well-defined against a value task instance | ||||
|         /// </summary> | ||||
|         public bool HasTask | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source != null; | ||||
|         } | ||||
|  | ||||
|         internal PooledValueTask<T> PooledTask | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source == null ? new PooledValueTask<T>(_value) : new PooledValueTask<T>(_source, _token); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Rents a task-source that will be recycled when the task is awaited | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static PooledValueTaskSource<T> Create() | ||||
|         { | ||||
|             var source = PooledState<T>.Create(out var token); | ||||
|             return new PooledValueTaskSource<T>(source, token); | ||||
|         } | ||||
|  | ||||
|         private readonly PooledState<T>? _source; | ||||
|         private readonly short _token; | ||||
|  | ||||
|         private readonly T _value; | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal PooledValueTaskSource(PooledState<T> source, short token) | ||||
|         { | ||||
|             _source = source; | ||||
|             _token = token; | ||||
|             _value = default!; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Create a new PooledValueTaskSource that will yield a constant value without ever renting/recycling any background state | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public PooledValueTaskSource(T value) | ||||
|         { | ||||
|             _source = null; | ||||
|             _token = default; | ||||
|             _value = value!; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Test whether the source is valid | ||||
|         /// </summary> | ||||
|         public bool IsValid | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source?.IsValid(_token) == true; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetResult(T result) => _source?.TrySetResult(result, _token) == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetResult(T result) | ||||
|         { | ||||
|             if (!TrySetResult(result)) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetException(Exception error) => _source?.TrySetException(error, _token) == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetException(Exception exception) | ||||
|         { | ||||
|             if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetCanceled() => _source?.TrySetCanceled(_token) == true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetCanceled() | ||||
|         { | ||||
|             if (!TrySetCanceled()) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Indicates whether this is an invalid default instance | ||||
|         /// </summary> | ||||
|         public bool IsNull | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source == null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,85 @@ | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A ValueTask<typeparamref name="T"/> with a custom source and builder | ||||
|     /// </summary> | ||||
|     [AsyncMethodBuilder(typeof(MethodBuilders.PooledValueTaskMethodBuilder<>))] | ||||
|     public readonly struct PooledValueTask<T> | ||||
|     { | ||||
|         /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||
|         public override bool Equals(object? obj) => obj is PooledValueTask<T> pvt && _source == pvt._source && _token == pvt._token; | ||||
|         /// <summary><see cref="Object.GetHashCode"/></summary> | ||||
|         public override int GetHashCode() => (_source == null ? 0 : _source.GetHashCode()) ^ _token; | ||||
|         /// <summary><see cref="Object.ToString"/></summary> | ||||
|         public override string ToString() => nameof(PooledValueTask); | ||||
|  | ||||
|         private readonly PooledState<T>? _source; | ||||
|         private readonly short _token; | ||||
|         private readonly T _result; | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal PooledValueTask(PooledState<T> source, short token) | ||||
|         { | ||||
|             _source = source; | ||||
|             _token = token; | ||||
|             _result = default!; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Creates a value-task with a fixed value | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public PooledValueTask(T result) | ||||
|         { | ||||
|             _source = default; | ||||
|             _token = default; | ||||
|             _result = result; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a value-task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public ValueTask<T> AsValueTask() => _source == null ? new ValueTask<T>(_result) : new ValueTask<T>(_source, _token); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a value-task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static implicit operator ValueTask<T>(in PooledValueTask<T> task) => task.AsValueTask(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the awaiter for the task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| #pragma warning disable CA2012 // 正确使用 ValueTask | ||||
|         public ValueTaskAwaiter<T> GetAwaiter() => AsValueTask().GetAwaiter(); | ||||
| #pragma warning restore CA2012 // 正确使用 ValueTask | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the configured awaiter for the task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public ConfiguredValueTaskAwaitable<T> ConfigureAwait(bool continueOnCapturedContext) | ||||
|             => AsValueTask().ConfigureAwait(continueOnCapturedContext); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Rents a task-source that will be recycled when the task is awaited | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal static PooledValueTaskSource<T> CreateSource() => PooledValueTaskSource<T>.Create(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Indicates whether this is an invalid default instance | ||||
|         /// </summary> | ||||
|         public bool IsNull | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _source == null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,5 @@ | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| [assembly: InternalsVisibleTo("Benchmark, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d136a87a0c11ded39332f7ead1f2ce53256a42a350ee847df1dc520bc781210a5f1fe73b3f91a4001fdcfa35340a212e70443de46e6928510f57a55f4ab9b95257f5067d4de4be8cdad460781866b20a6ca20f66302df61b52e55e16c080cad95aae8b94ffdd54718b9963d0240b0d0d5afe655b05c91771f7dc8a314387d3c3")] | ||||
|  | ||||
| [assembly: InternalsVisibleTo("PooledAwait.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d136a87a0c11ded39332f7ead1f2ce53256a42a350ee847df1dc520bc781210a5f1fe73b3f91a4001fdcfa35340a212e70443de46e6928510f57a55f4ab9b95257f5067d4de4be8cdad460781866b20a6ca20f66302df61b52e55e16c080cad95aae8b94ffdd54718b9963d0240b0d0d5afe655b05c91771f7dc8a314387d3c3")] | ||||
| @@ -0,0 +1,218 @@ | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| using PooledAwait.Internal; | ||||
|  | ||||
| #if !NETSTANDARD1_3 | ||||
| using System.Reflection; | ||||
| #endif | ||||
|  | ||||
| namespace PooledAwait | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Lightweight implementation of TaskCompletionSource<typeparamref name="T"/> | ||||
|     /// </summary> | ||||
|     /// <remarks>When possible, this will bypass TaskCompletionSource<typeparamref name="T"/> completely</remarks> | ||||
|     public readonly struct ValueTaskCompletionSource<T> | ||||
|     { | ||||
|         /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||
|         public override bool Equals(object? obj) => obj is ValueTaskCompletionSource<T> other && _state == other._state; | ||||
|         /// <summary><see cref="Object.GetHashCode"/></summary> | ||||
|         public override int GetHashCode() => _state == null ? 0 : _state.GetHashCode(); | ||||
|         /// <summary><see cref="Object.ToString"/></summary> | ||||
|         public override string ToString() => "ValueTaskCompletionSource"; | ||||
|  | ||||
| #if !NETSTANDARD1_3 | ||||
|         private static readonly Func<Task<T>, Exception, bool>? s_TrySetException = TryCreate<Exception>(nameof(TrySetException)); | ||||
|         private static readonly Func<Task<T>, T, bool>? s_TrySetResult = TryCreate<T>(nameof(TrySetResult)); | ||||
|         private static readonly Func<Task<T>, CancellationToken, bool>? s_TrySetCanceled = TryCreate<CancellationToken>(nameof(TrySetCanceled)); | ||||
|         private static readonly bool s_Optimized = ValidateOptimized(); | ||||
| #endif | ||||
|         private readonly object _state; | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         private ValueTaskCompletionSource(object state) => _state = state; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the instance as a task | ||||
|         /// </summary> | ||||
|         public Task<T> Task | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _state as Task<T> ?? ((TaskCompletionSource<T>)_state).Task; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Indicates whether this is an invalid default instance | ||||
|         /// </summary> | ||||
|         public bool IsNull | ||||
|         { | ||||
|             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|             get => _state == null; | ||||
|         } | ||||
|  | ||||
|         internal bool IsOptimized => _state is Task<T>; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Create an instance pointing to a new task | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public static ValueTaskCompletionSource<T> Create() => | ||||
| #if !NETSTANDARD1_3 | ||||
|             s_Optimized ? CreateOptimized() : | ||||
| #endif | ||||
|             CreateFallback(); | ||||
|  | ||||
| #if !NETSTANDARD1_3 | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal static ValueTaskCompletionSource<T> CreateOptimized() | ||||
|         { | ||||
|             Counters.TaskAllocated.Increment(); | ||||
|             return new ValueTaskCompletionSource<T>(new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously)); | ||||
|         } | ||||
| #endif | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         internal static ValueTaskCompletionSource<T> CreateFallback() | ||||
|         { | ||||
|             Counters.TaskAllocated.Increment(); | ||||
|             return new ValueTaskCompletionSource<T>(new TaskCompletionSource<T>()); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the outcome of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetException(Exception exception) | ||||
|         { | ||||
| #if !NETSTANDARD1_3 | ||||
|             if (_state is Task<T> task) | ||||
|             { | ||||
|                 var result = s_TrySetException!(task, exception); | ||||
|                 if (!result && !task.IsCompleted) SpinUntilCompleted(task); | ||||
|                 return result; | ||||
|             } | ||||
| #endif | ||||
|             return _state != null && ((TaskCompletionSource<T>)_state).TrySetException(exception); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetException(Exception exception) | ||||
|         { | ||||
|             if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the outcome of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetCanceled(CancellationToken cancellationToken = default) | ||||
|         { | ||||
| #if !NETSTANDARD1_3 | ||||
|             if (_state is Task<T> task) | ||||
|             { | ||||
|                 var result = s_TrySetCanceled!(task, cancellationToken); | ||||
|                 if (!result && !task.IsCompleted) SpinUntilCompleted(task); | ||||
|                 return result; | ||||
|             } | ||||
| #endif | ||||
|             return _state != null && ((TaskCompletionSource<T>)_state).TrySetCanceled( | ||||
| #if !NET45 | ||||
|                 cancellationToken | ||||
| #endif | ||||
|                 ); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetCanceled(CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the outcome of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public bool TrySetResult(T value) | ||||
|         { | ||||
| #if !NETSTANDARD1_3 | ||||
|             if (_state is Task<T> task) | ||||
|             { | ||||
|                 var result = s_TrySetResult!(task, value); | ||||
|                 if (!result && !task.IsCompleted) SpinUntilCompleted(task); | ||||
|                 return result; | ||||
|             } | ||||
| #endif | ||||
|             return _state != null && ((TaskCompletionSource<T>)_state).TrySetResult(value); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Set the result of the operation | ||||
|         /// </summary> | ||||
|         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|         public void SetResult(T value) | ||||
|         { | ||||
|             if (!TrySetResult(value)) ThrowHelper.ThrowInvalidOperationException(); | ||||
|         } | ||||
|  | ||||
| #if !NETSTANDARD1_3 | ||||
|         [MethodImpl(MethodImplOptions.NoInlining)] | ||||
|         private static Func<Task<T>, TArg, bool>? TryCreate<TArg>(string methodName) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 return (Func<Task<T>, TArg, bool>)Delegate.CreateDelegate( | ||||
|                 typeof(Func<Task<T>, TArg, bool>), | ||||
|                 typeof(Task<T>).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, | ||||
|                     null, new[] { typeof(TArg) }, null)!); | ||||
|             } | ||||
|             catch { return null; } | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.NoInlining)] | ||||
|         private static bool ValidateOptimized() | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 // perform feature tests of our voodoo | ||||
|                 var source = CreateOptimized(); | ||||
|                 var task = source.Task; | ||||
|                 if (task == null) return false; | ||||
|                 if (task.IsCompleted) return false; | ||||
|  | ||||
|                 if (!source.TrySetResult(default!)) return false; | ||||
|                 if (task.Status != TaskStatus.RanToCompletion) return false; | ||||
|  | ||||
|                 source = CreateOptimized(); | ||||
|                 task = source.Task; | ||||
|                 if (!source.TrySetException(new InvalidOperationException())) return false; | ||||
|                 if (!task.IsCompleted) return false; | ||||
|                 if (!task.IsFaulted) return false; | ||||
|                 try | ||||
|                 { | ||||
|                     _ = task.Result; | ||||
|                     return false; | ||||
|                 } | ||||
|                 catch (AggregateException ex) when (ex.InnerException is InvalidOperationException) { } | ||||
|                 if (!(task.Exception?.InnerException is InvalidOperationException)) return false; | ||||
|                 return true; | ||||
|             } | ||||
|             catch { return false; } | ||||
|         } | ||||
|  | ||||
|         [MethodImpl(MethodImplOptions.NoInlining)] | ||||
|         private void SpinUntilCompleted(Task<T> task) | ||||
|         { | ||||
|             // Spin wait until the completion is finalized by another thread. | ||||
|             var sw = new SpinWait(); | ||||
|             while (!task.IsCompleted) | ||||
|                 sw.SpinOnce(); | ||||
|         } | ||||
| #endif | ||||
|     } | ||||
| } | ||||
| @@ -580,7 +580,19 @@ public static class Reflect | ||||
|  | ||||
|         return func; | ||||
|     } | ||||
|     /// <summary>把一个方法转为泛型委托,便于快速反射调用</summary> | ||||
|     /// <typeparam name="TFunc"></typeparam> | ||||
|     /// <param name="method"></param> | ||||
|     /// <param name="target"></param> | ||||
|     /// <returns></returns> | ||||
|     public static void RemoveCache<TFunc>(this MethodInfo method, object? target = null) where TFunc : class | ||||
|     { | ||||
|         if (method == null) return; | ||||
|  | ||||
|         var key = new DelegateCacheKey(method, typeof(TFunc), target); | ||||
|  | ||||
|         DelegateCache<TFunc>.Cache.TryRemove(key); | ||||
|     } | ||||
|     #endregion | ||||
| } | ||||
| public static class DelegateCache<TFunc> | ||||
|   | ||||
| @@ -24,7 +24,7 @@ | ||||
| 		<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<PropertyGroup Condition="'$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net5.0-windows' or '$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net7.0-windows' or '$(TargetFramework)'=='net8.0-windows'"> | ||||
| 	<PropertyGroup Condition="'$(TargetFramework)'=='net47' or '$(TargetFramework)'=='net5.0-windows' or '$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net7.0-windows' or '$(TargetFramework)'=='net8.0-windows'"> | ||||
| 		<DefineConstants>__WIN__</DefineConstants> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| @@ -54,7 +54,26 @@ | ||||
| 		<PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<PropertyGroup Condition="$(TargetFramework)=='net6.0'"> | ||||
| 		<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants> | ||||
| 	</PropertyGroup> | ||||
| 	<PropertyGroup Condition="$(TargetFramework)=='net8.0'"> | ||||
| 		<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<PropertyGroup Condition="$(TargetFramework)=='netstandard2.0'"> | ||||
| 		<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants> | ||||
| 		<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<PropertyGroup Condition="$(TargetFramework)=='net47'"> | ||||
| 		<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants> | ||||
| 		<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" Condition="$(IncludeAsyncInterfaces)=='true'" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<!--<ItemGroup Condition="'$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net6.0' "> | ||||
| 		<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="6.0.36" /> | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using System.Diagnostics; | ||||
| using PooledAwait; | ||||
|  | ||||
| using ThingsGateway.NewLife.Collections; | ||||
| using ThingsGateway.NewLife.Log; | ||||
| @@ -65,8 +65,21 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|     public static TimeProvider GlobalTimeProvider { get; set; } = TimeProvider.System; | ||||
|     #endregion | ||||
|     #region 构造 | ||||
|     private TimerScheduler(String name) => Name = name; | ||||
|  | ||||
|     private TimerScheduler(String name) | ||||
|     { | ||||
|         Name = name; | ||||
|         _processCallback = state => | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 Execute(state); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 XTrace.WriteException(ex, "Timer执行错误"); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|     /// <summary>销毁</summary> | ||||
|     public void Dispose() | ||||
|     { | ||||
| @@ -191,6 +204,13 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|                 Count--; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         timer.Method.RemoveCache<TimerCallback>(timer.Target.Target); | ||||
| #if NET6_0_OR_GREATER | ||||
|         timer.Method.RemoveCache<Func<Object?, ValueTask>>(timer.Target.Target); | ||||
| #endif | ||||
|         timer.Method.RemoveCache<Func<Object?, Task>>(timer.Target.Target); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private AutoResetEvent? _waitForTimer; | ||||
| @@ -244,24 +264,14 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|                         // 必须在主线程设置状态,否则可能异步线程还没来得及设置开始状态,主线程又开始了新的一轮调度 | ||||
|                         timer.Calling = true; | ||||
|                         if (timer.IsAsyncTask) | ||||
|                             ExecuteAsync(timer); | ||||
|                             _ = ExecuteAsync(timer); | ||||
|                         //Task.Factory.StartNew(ExecuteAsync, timer, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); | ||||
|                         else if (!timer.Async) | ||||
|                             Execute(timer); | ||||
|                         else | ||||
|                             //Task.Factory.StartNew(() => ProcessItem(timer)); | ||||
|                             // 不需要上下文流动,捕获所有异常 | ||||
|                             ThreadPool.UnsafeQueueUserWorkItem(s => | ||||
|                             { | ||||
|                                 try | ||||
|                                 { | ||||
|                                     Execute(s); | ||||
|                                 } | ||||
|                                 catch (Exception ex) | ||||
|                                 { | ||||
|                                     XTrace.WriteException(ex); | ||||
|                                 } | ||||
|                             }, timer); | ||||
|                             ThreadPool.UnsafeQueueUserWorkItem(_processCallback, timer); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -276,7 +286,7 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|  | ||||
|         WriteLog("调度线程已退出:{0}", Name); | ||||
|     } | ||||
|  | ||||
|     private readonly WaitCallback _processCallback; | ||||
|     /// <summary>检查定时器是否到期</summary> | ||||
|     /// <param name="timer"></param> | ||||
|     /// <param name="now"></param> | ||||
| @@ -318,9 +328,10 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|  | ||||
|         timer.hasSetNext = false; | ||||
|  | ||||
|         using var span = timer.Tracer?.NewSpan(timer.TracerName ?? $"timer:Execute", timer.Timers + ""); | ||||
|         var sw = _stopwatchPool.Get(); | ||||
|         sw.Restart(); | ||||
|         //string tracerName = timer.TracerName ?? "timer:ExecuteAsync"; | ||||
|         //string timerArg = timer.Timers.ToString(); | ||||
|         //using var span = timer.Tracer?.NewSpan(tracerName, timerArg); | ||||
|         var sw = ValueStopwatch.StartNew(); | ||||
|         try | ||||
|         { | ||||
|             // 弱引用判断 | ||||
| @@ -331,92 +342,106 @@ public class TimerScheduler : IDisposable, ILogFeature | ||||
|                 timer.Dispose(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             var func = timer.Method.As<TimerCallback>(target); | ||||
|             func!(timer.State); | ||||
|             if (timer.TimerCallbackCachedDelegate == null) | ||||
|             { | ||||
|                 timer.TimerCallbackCachedDelegate = timer.Method.As<TimerCallback>(target); | ||||
|             } | ||||
|             //var func = timer.Method.As<TimerCallback>(target); | ||||
|             timer.TimerCallbackCachedDelegate!(timer.State); | ||||
|         } | ||||
|         catch (ThreadAbortException) { throw; } | ||||
|         catch (ThreadInterruptedException) { throw; } | ||||
|         // 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事 | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             span?.SetError(ex, null); | ||||
|             //span?.SetError(ex, null); | ||||
|             XTrace.WriteException(ex); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             sw.Stop(); | ||||
|  | ||||
|             OnExecuted(timer, (Int32)sw.ElapsedMilliseconds); | ||||
|  | ||||
|             _stopwatchPool.Return(sw); | ||||
|             var ms = sw.GetElapsedTime().TotalMilliseconds; | ||||
|             OnExecuted(timer, (Int32)ms); | ||||
|         } | ||||
|     } | ||||
|     private static ObjectPool<Stopwatch> _stopwatchPool { get; } = new ObjectPool<Stopwatch>(); | ||||
|  | ||||
|     /// <summary>处理每一个定时器</summary> | ||||
|     /// <param name="state"></param> | ||||
|     private async void ExecuteAsync(Object? state) | ||||
|     private Task ExecuteAsync(Object? state) | ||||
|     { | ||||
|         if (state is not TimerX timer) return; | ||||
|  | ||||
|         //TimerX.Current = timer; | ||||
|  | ||||
|         // 控制日志显示 | ||||
|         //WriteLogEventArgs.CurrentThreadName = Name == "Default" ? "T" : Name; | ||||
|  | ||||
|         timer.hasSetNext = false; | ||||
|  | ||||
|         using var span = timer.Tracer?.NewSpan(timer.TracerName ?? $"timer:ExecuteAsync", timer.Timers + ""); | ||||
|         var sw = _stopwatchPool.Get(); | ||||
|         sw.Restart(); | ||||
|         try | ||||
|         return ExecuteAsync(this, state); | ||||
|         static async PooledTask ExecuteAsync(TimerScheduler @this, Object? state) | ||||
|         { | ||||
|             // 弱引用判断 | ||||
|             var target = timer.Target.Target; | ||||
|             if (target == null && !timer.Method.IsStatic) | ||||
|             if (state is not TimerX timer) return; | ||||
|  | ||||
|             //TimerX.Current = timer; | ||||
|  | ||||
|             // 控制日志显示 | ||||
|             //WriteLogEventArgs.CurrentThreadName = Name == "Default" ? "T" : Name; | ||||
|  | ||||
|             timer.hasSetNext = false; | ||||
|  | ||||
|             //string tracerName = timer.TracerName ?? "timer:ExecuteAsync"; | ||||
|             //string timerArg = timer.Timers.ToString(); | ||||
|             //using var span = timer.Tracer?.NewSpan(tracerName, timerArg); | ||||
|             var sw = ValueStopwatch.StartNew(); | ||||
|             try | ||||
|             { | ||||
|                 Remove(timer, "委托已不存在(GC回收委托所在对象)"); | ||||
|                 timer.Dispose(); | ||||
|                 return; | ||||
|             } | ||||
|                 // 弱引用判断 | ||||
|                 var target = timer.Target.Target; | ||||
|                 if (target == null && !timer.Method.IsStatic) | ||||
|                 { | ||||
|                     @this.Remove(timer, "委托已不存在(GC回收委托所在对象)"); | ||||
|                     timer.Dispose(); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|  | ||||
|  | ||||
| #if NET6_0_OR_GREATER | ||||
|             if (timer.IsValueTask) | ||||
|             { | ||||
|                 var func = timer.Method.As<Func<Object?, ValueTask>>(target); | ||||
|                 var task = func!(timer.State); | ||||
|                 if (!task.IsCompleted) | ||||
|                     await task.ConfigureAwait(false); | ||||
|             } | ||||
|             else | ||||
|                 if (timer.IsValueTask) | ||||
|                 { | ||||
|                     if (timer.ValueTaskCachedDelegate == null) | ||||
|                     { | ||||
|                         timer.ValueTaskCachedDelegate = timer.Method.As<Func<Object?, ValueTask>>(target); | ||||
|                     } | ||||
|  | ||||
|                     //var func = timer.Method.As<Func<Object?, ValueTask>>(target); | ||||
|                     var task = timer.ValueTaskCachedDelegate!(timer.State); | ||||
|                     if (!task.IsCompleted) | ||||
|                         await task.ConfigureAwait(false); | ||||
|                 } | ||||
|                 else | ||||
| #endif | ||||
|             { | ||||
|                 var func = timer.Method.As<Func<Object?, Task>>(target); | ||||
|                 var task = func!(timer.State); | ||||
|                 if (!task.IsCompleted) | ||||
|                     await task.ConfigureAwait(false); | ||||
|                 { | ||||
|                     if (timer.TaskCachedDelegate == null) | ||||
|                     { | ||||
|                         timer.TaskCachedDelegate = timer.Method.As<Func<Object?, Task>>(target); | ||||
|                     } | ||||
|  | ||||
|                     //var func = timer.Method.As<Func<Object?, Task>>(target); | ||||
|                     var task = timer.TaskCachedDelegate!(timer.State); | ||||
|                     if (!task.IsCompleted) | ||||
|                         await task.ConfigureAwait(false); | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|             catch (ThreadAbortException) { throw; } | ||||
|             catch (ThreadInterruptedException) { throw; } | ||||
|             // 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事 | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 //span?.SetError(ex, null); | ||||
|                 XTrace.WriteException(ex); | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 var ms = sw.GetElapsedTime().TotalMilliseconds; | ||||
|  | ||||
|         } | ||||
|         catch (ThreadAbortException) { throw; } | ||||
|         catch (ThreadInterruptedException) { throw; } | ||||
|         // 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事 | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             span?.SetError(ex, null); | ||||
|             XTrace.WriteException(ex); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             sw.Stop(); | ||||
|                 @this.OnExecuted(timer, (Int32)ms); | ||||
|  | ||||
|             OnExecuted(timer, (Int32)sw.ElapsedMilliseconds); | ||||
|  | ||||
|             _stopwatchPool.Return(sw); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void OnExecuted(TimerX timer, Int32 ms) | ||||
|     { | ||||
|         timer.Cost = timer.Cost == 0 ? ms : (timer.Cost + ms) / 2; | ||||
|   | ||||
| @@ -542,6 +542,12 @@ public class TimerX : ITimer, ITimerx, IDisposable | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public Func<object?, Task>? TaskCachedDelegate { get; internal set; } | ||||
| #if NET6_0_OR_GREATER | ||||
|     public Func<object?, ValueTask>? ValueTaskCachedDelegate { get; internal set; } | ||||
| #endif | ||||
|     public TimerCallback? TimerCallbackCachedDelegate { get; internal set; } | ||||
|  | ||||
|     private static void CopyNow(Object? state) => _Now = TimerScheduler.Default.GetNow(); | ||||
|     #endregion | ||||
|  | ||||
|   | ||||
| @@ -694,8 +694,12 @@ namespace ThingsGateway.SqlSugar | ||||
|             var enumerator = table.GetEnumerator(); | ||||
|             while (enumerator.MoveNext()) | ||||
|             { | ||||
|                 var cur = enumerator.Current; | ||||
|                 yield return cur.Value.Item2[rowIndex]; | ||||
|                 var kvp = enumerator.Current; | ||||
|                 var list = kvp.Value.Item2; | ||||
|                 if (list != null && rowIndex < list.Count) | ||||
|                     yield return list[rowIndex]; | ||||
|                 else | ||||
|                     yield return new DataInfos { ColumnName = kvp.Key, Value = DBNull.Value }; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|   | ||||
| @@ -201,6 +201,7 @@ namespace ThingsGateway.SqlSugar | ||||
|             { | ||||
|                 foreach (var column in columns) | ||||
|                 { | ||||
|  | ||||
|                     if (column.IsIgnore) | ||||
|                     { | ||||
|                         continue; | ||||
| @@ -210,6 +211,12 @@ namespace ThingsGateway.SqlSugar | ||||
|                     { | ||||
|                         name = column.PropertyName; | ||||
|                     } | ||||
|                     if (!results.TryGetValue(name, out var tuple) || tuple.Item2 == null) | ||||
|                     { | ||||
|                         // 某些列可能不在 DataTable 中(例如数据库多了列) | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     var value = ValueConverter(column, GetValue(item, column)); | ||||
|                     if (column.SqlParameterDbType != null && column.SqlParameterDbType is Type && UtilMethods.HasInterface((Type)column.SqlParameterDbType, typeof(ISugarDataConverter))) | ||||
|                     { | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| using System.Data; | ||||
| using System.Data.Common; | ||||
| using System.Numerics; | ||||
|  | ||||
| using ThingsGateway.NewLife.Reflection; | ||||
| namespace ThingsGateway.SqlSugar | ||||
|   | ||||
| @@ -37,7 +37,7 @@ | ||||
| 		<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.2" /> | ||||
| 		<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" /> | ||||
| 		<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="8.0.2" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="9.0.10" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| <Project> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<PluginVersion>10.11.107</PluginVersion> | ||||
| 		<ProPluginVersion>10.11.107</ProPluginVersion> | ||||
| 		<DefaultVersion>10.11.107</DefaultVersion> | ||||
| 		<AuthenticationVersion>10.11.6</AuthenticationVersion> | ||||
| 		<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion> | ||||
| 		<PluginVersion>10.12.18</PluginVersion> | ||||
| 		<ProPluginVersion>10.12.18</ProPluginVersion> | ||||
| 		<DefaultVersion>10.12.18</DefaultVersion> | ||||
| 		<AuthenticationVersion>10.11.7</AuthenticationVersion> | ||||
| 		<SourceGeneratorVersion>10.11.7</SourceGeneratorVersion> | ||||
| 		<NET8Version>8.0.21</NET8Version> | ||||
| 		<NET10Version>10.0.0-rc.2.25502.107</NET10Version> | ||||
| 		<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages> | ||||
| 		<IsTrimmable>false</IsTrimmable> | ||||
| 		<ManagementProPluginVersion>10.11.87</ManagementProPluginVersion> | ||||
| 		<ManagementPluginVersion>10.11.87</ManagementPluginVersion> | ||||
| 		<TSVersion>4.0.0-beta.120</TSVersion> | ||||
| 		<TSVersion>4.0.0-rc.5</TSVersion> | ||||
| 		 | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
|   | ||||
| @@ -10,53 +10,53 @@ | ||||
| <div class="w-100" style=@($"height:{HeightString}")> | ||||
|  | ||||
|     <Card HeaderText=@HeaderText class=@("w-100 h-100")> | ||||
|     <HeaderTemplate> | ||||
|         <div class="flex-fill"> | ||||
|         </div> | ||||
|         <HeaderTemplate> | ||||
|             <div class="flex-fill"> | ||||
|             </div> | ||||
|  | ||||
|         @if (LogLevelChanged.HasDelegate) | ||||
|         { | ||||
|             <Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select> | ||||
|         } | ||||
|         <Tooltip class=" col-auto" Title="@RazorLocalizer[Pause?"Play":"Pause"]" Placement="Placement.Bottom"> | ||||
|             @if (LogLevelChanged.HasDelegate) | ||||
|             { | ||||
|                 <Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select> | ||||
|             } | ||||
|             <Tooltip class=" col-auto" Title=@(Pause? PlayText:PauseText) Placement="Placement.Bottom"> | ||||
|  | ||||
|             <Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(Pause?"fa fa-play":"fa fa-pause") OnClick="OnPause" /> | ||||
|                 <Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(Pause ? "fa fa-play" : "fa fa-pause") OnClick="OnPause" /> | ||||
|  | ||||
|         </Tooltip> | ||||
|             </Tooltip> | ||||
|  | ||||
|         <Tooltip class=" col-auto" Title="@RazorLocalizer["Export"]" Placement="Placement.Bottom"> | ||||
|             <Tooltip class=" col-auto" Title="@ExportText" Placement="Placement.Bottom"> | ||||
|  | ||||
|             <Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" /> | ||||
|                 <Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" /> | ||||
|  | ||||
|         </Tooltip> | ||||
|             </Tooltip> | ||||
|  | ||||
|         <Tooltip class=" col-auto" Title="@RazorLocalizer["Delete"]" Placement="Placement.Bottom"> | ||||
|             <Tooltip class=" col-auto" Title="@DeleteText" Placement="Placement.Bottom"> | ||||
|  | ||||
|             <Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" /> | ||||
|                 <Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" /> | ||||
|  | ||||
|         </Tooltip> | ||||
|             </Tooltip> | ||||
|  | ||||
|  | ||||
|     </HeaderTemplate> | ||||
|     <BodyTemplate> | ||||
|                 <div style=@($"height:calc(100% - 50px);overflow-y:scroll;flex-fill;")> | ||||
|             <Virtualize Items="CurrentMessages??new  List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2> | ||||
|                 <ItemContent> | ||||
|                     @*       <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@ | ||||
|                     <div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"": | ||||
|                          itemMessage.Level>=(byte)Microsoft.Extensions.Logging.LogLevel.Warning? " red--text text-truncate":"green--text text-truncate") | ||||
|                          title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> | ||||
|         </HeaderTemplate> | ||||
|         <BodyTemplate> | ||||
|             <div style=@($"height:calc(100% - 50px);overflow-y:scroll;flex-fill;")> | ||||
|                 <Virtualize Items="CurrentMessages ?? new List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2> | ||||
|                     <ItemContent> | ||||
|                         @*       <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@ | ||||
|                         <div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"": | ||||
|                                                           itemMessage.Level >= (byte)Microsoft.Extensions.Logging.LogLevel.Warning ? " red--text text-truncate" : "green--text text-truncate") | ||||
|                              title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> | ||||
|  | ||||
|                         @itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150)) | ||||
|                             @itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150)) | ||||
|  | ||||
|                     </div> | ||||
|                     @* </Tooltip> *@ | ||||
|                 </ItemContent> | ||||
|             </Virtualize> | ||||
|         </div> | ||||
|                         </div> | ||||
|                         @* </Tooltip> *@ | ||||
|                     </ItemContent> | ||||
|                 </Virtualize> | ||||
|             </div> | ||||
|  | ||||
|     </BodyTemplate> | ||||
| </Card> | ||||
|         </BodyTemplate> | ||||
|     </Card> | ||||
|  | ||||
|  | ||||
| </div> | ||||
|   | ||||
| @@ -10,12 +10,11 @@ | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Web; | ||||
|  | ||||
| using System.Diagnostics; | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| using ThingsGateway.Extension; | ||||
| using ThingsGateway.Foundation; | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Threading; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| @@ -23,6 +22,24 @@ namespace ThingsGateway.Debug; | ||||
|  | ||||
| public partial class LogConsole : IDisposable | ||||
| { | ||||
|  | ||||
|     private string PlayText { get; set; } | ||||
|     private string PauseText { get; set; } | ||||
|     private string ExportText { get; set; } | ||||
|     private string DeleteText { get; set; } | ||||
|  | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         PlayText = RazorLocalizer["Play"]; | ||||
|         PauseText = RazorLocalizer["Pause"]; | ||||
|         ExportText = RazorLocalizer["Export"]; | ||||
|         DeleteText = RazorLocalizer["Delete"]; | ||||
|  | ||||
|         _Timer = new TimerX(RunTimerAsync, null, 1_000, 1_000, nameof(LogConsole)) { Async = true }; | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|     private TimerX _Timer; | ||||
|  | ||||
|     private bool Pause; | ||||
|  | ||||
|     public bool Disposed { get; set; } | ||||
| @@ -69,7 +86,7 @@ public partial class LogConsole : IDisposable | ||||
|         { | ||||
|             logPath = LogPath; | ||||
|             Messages = new List<LogMessage>(); | ||||
|             await ExecuteAsync(); | ||||
|             _Timer?.SetNext(0); | ||||
|         } | ||||
|  | ||||
|         await base.OnParametersSetAsync(); | ||||
| @@ -82,63 +99,38 @@ public partial class LogConsole : IDisposable | ||||
|     public void Dispose() | ||||
|     { | ||||
|         Disposed = true; | ||||
|         _Timer?.SafeDispose(); | ||||
|         GC.SuppressFinalize(this); | ||||
|     } | ||||
|     private WaitLock WaitLock = new(nameof(LogConsole)); | ||||
|     protected async Task ExecuteAsync() | ||||
|     protected async ValueTask ExecuteAsync() | ||||
|     { | ||||
|         if (WaitLock.Waited) return; | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync(); | ||||
|             await Task.Delay(1000); | ||||
|  | ||||
|             if (LogPath != null) | ||||
|         if (LogPath != null) | ||||
|         { | ||||
|             var files = await TextFileReadService.GetLogFilesAsync(LogPath); | ||||
|             if (!files.IsSuccess) | ||||
|             { | ||||
|                 var files = await TextFileReadService.GetLogFilesAsync(LogPath); | ||||
|                 if (!files.IsSuccess) | ||||
|                 Messages = new List<LogMessage>(); | ||||
|                 await Task.Delay(1000); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|  | ||||
|                 var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault()); | ||||
|                 if (result.IsSuccess) | ||||
|                 { | ||||
|                     Messages = new List<LogMessage>(); | ||||
|                     await Task.Delay(1000); | ||||
|                     Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToArray(); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     await Task.Run(async () => | ||||
|                     { | ||||
|                         Stopwatch sw = Stopwatch.StartNew(); | ||||
|                         var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault()); | ||||
|                         if (result.IsSuccess) | ||||
|                         { | ||||
|                             Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList(); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             Messages = new List<LogMessage>(); | ||||
|                         } | ||||
|                         sw.Stop(); | ||||
|                         if (sw.ElapsedMilliseconds > 500) | ||||
|                         { | ||||
|                             await Task.Delay(1000); | ||||
|                         } | ||||
|                     }); | ||||
|                     Messages = Array.Empty<LogMessage>(); | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             NewLife.Log.XTrace.WriteException(ex); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             WaitLock.Release(); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         _ = RunTimerAsync(); | ||||
|         base.OnInitialized(); | ||||
|     } | ||||
|  | ||||
|     private async Task Delete() | ||||
|     { | ||||
| @@ -185,19 +177,9 @@ public partial class LogConsole : IDisposable | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
|  | ||||
|     private async Task RunTimerAsync() | ||||
|     private async Task RunTimerAsync(object? state) | ||||
|     { | ||||
|         while (!Disposed) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await ExecuteAsync(); | ||||
|                 await InvokeAsync(StateHasChanged); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 NewLife.Log.XTrace.WriteException(ex); | ||||
|             } | ||||
|         } | ||||
|         await ExecuteAsync(); | ||||
|         await InvokeAsync(StateHasChanged); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -11,6 +11,8 @@ | ||||
| using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| using PooledAwait; | ||||
|  | ||||
| using System.Linq.Expressions; | ||||
|  | ||||
| using ThingsGateway.Gateway.Application.Extensions; | ||||
| @@ -139,18 +141,27 @@ public abstract class VariableObject | ||||
|     /// <summary> | ||||
|     /// <see cref="VariableRuntimeAttribute"/>特性连读,反射赋值到继承类中的属性 | ||||
|     /// </summary> | ||||
|     public virtual async ValueTask<OperResult> MultiReadAsync(CancellationToken cancellationToken = default) | ||||
|     public virtual ValueTask<OperResult> MultiReadAsync(CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             GetVariableSources(); | ||||
|             //连读 | ||||
|             foreach (var item in DeviceVariableSourceReads) | ||||
|             return MultiReadAsync(this, cancellationToken); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return EasyValueTask.FromResult(new OperResult(ex)); | ||||
|         } | ||||
|  | ||||
|         static async PooledValueTask<OperResult> MultiReadAsync(VariableObject @this, CancellationToken cancellationToken) | ||||
|         { | ||||
|             foreach (var item in @this.DeviceVariableSourceReads) | ||||
|             { | ||||
|                 var result = await Device.ReadAsync(item.RegisterAddress, item.Length, cancellationToken).ConfigureAwait(false); | ||||
|                 var result = await @this.Device.ReadAsync(item.RegisterAddress, item.Length, cancellationToken).ConfigureAwait(false); | ||||
|                 if (result.IsSuccess) | ||||
|                 { | ||||
|                     var result1 = item.VariableRuntimes.PraseStructContent(Device, result.Content.Span, exWhenAny: true); | ||||
|                     var result1 = item.VariableRuntimes.PraseStructContent(@this.Device, result.Content.Span, exWhenAny: true); | ||||
|                     if (!result1.IsSuccess) | ||||
|                     { | ||||
|                         item.LastErrorMessage = result1.ErrorMessage; | ||||
| @@ -168,13 +179,9 @@ public abstract class VariableObject | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             SetValue(); | ||||
|             @this.SetValue(); | ||||
|             return OperResult.Success; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new OperResult(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -205,30 +212,31 @@ public abstract class VariableObject | ||||
|     /// <param name="propertyName">属性名称,必须使用<see cref="VariableRuntimeAttribute"/>特性</param> | ||||
|     /// <param name="value">写入值</param> | ||||
|     /// <param name="cancellationToken">取消令箭</param> | ||||
|     public virtual async ValueTask<OperResult> WriteValueAsync(string propertyName, object value, CancellationToken cancellationToken = default) | ||||
|     public virtual ValueTask<OperResult> WriteValueAsync(string propertyName, object value, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             GetVariableSources(); | ||||
|             if (string.IsNullOrEmpty(propertyName)) | ||||
|             { | ||||
|                 return new OperResult($"PropertyName cannot be null or empty."); | ||||
|                 return EasyValueTask.FromResult(new OperResult($"PropertyName cannot be null or empty.")); | ||||
|             } | ||||
|  | ||||
|             if (!VariableRuntimePropertyDict.TryGetValue(propertyName, out var variableRuntimeProperty)) | ||||
|             { | ||||
|                 return new OperResult($"This attribute is not recognized and may not have been identified using the {typeof(VariableRuntimeAttribute)} attribute"); | ||||
|                 return EasyValueTask.FromResult(new OperResult($"This attribute is not recognized and may not have been identified using the {typeof(VariableRuntimeAttribute)} attribute")); | ||||
|             } | ||||
|  | ||||
|             JToken jToken = GetExpressionsValue(value, variableRuntimeProperty); | ||||
|  | ||||
|             var result = await Device.WriteJTokenAsync(variableRuntimeProperty.VariableClass.RegisterAddress, jToken, variableRuntimeProperty.VariableClass.DataType, cancellationToken).ConfigureAwait(false); | ||||
|             return result; | ||||
|             return Device.WriteJTokenAsync(variableRuntimeProperty.VariableClass.RegisterAddress, jToken, variableRuntimeProperty.VariableClass.DataType, cancellationToken); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new OperResult(ex); | ||||
|             return EasyValueTask.FromResult(new OperResult(ex)); | ||||
|         } | ||||
|  | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -8,6 +8,8 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using PooledAwait; | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| using TouchSocket.Resources; | ||||
| @@ -138,82 +140,86 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel | ||||
|  | ||||
|     private DeviceSingleStreamDataHandleAdapter<DDPTcpMessage> DDPAdapter = new(); | ||||
|  | ||||
|     protected override async ValueTask<bool> OnTcpReceiving(IBytesReader byteBlock) | ||||
|     protected override ValueTask<bool> OnTcpReceiving(IBytesReader byteBlock) | ||||
|     { | ||||
|  | ||||
|         if (DDPAdapter.TryParseRequest(ref byteBlock, out var message)) | ||||
|         { | ||||
|             return true; | ||||
|             return EasyValueTask.FromResult(true); | ||||
|         } | ||||
|         return OnTcpReceiving(this, message); | ||||
|  | ||||
|         if (message != null) | ||||
|         static async PooledValueTask<bool> OnTcpReceiving(DDPTcpSessionClientChannel @this, DDPTcpMessage message) | ||||
|         { | ||||
|             if (message.IsSuccess) | ||||
|             if (message != null) | ||||
|             { | ||||
|                 var id = $"ID={message.Id}"; | ||||
|                 if (message.Type == 0x09) | ||||
|                 if (message.IsSuccess) | ||||
|                 { | ||||
|                     var reader = new ClassBytesReader(message.Content); | ||||
|  | ||||
|                     if (this.DataHandlingAdapter == null) | ||||
|                     var id = $"ID={message.Id}"; | ||||
|                     if (message.Type == 0x09) | ||||
|                     { | ||||
|                         await this.OnTcpReceived(new ReceivedDataEventArgs(message.Content, default)).ConfigureAwait(false); | ||||
|                         var reader = new ClassBytesReader(message.Content); | ||||
|  | ||||
|                         if (@this.DataHandlingAdapter == null) | ||||
|                         { | ||||
|                             await @this.OnTcpReceived(new ReceivedDataEventArgs(message.Content, default)).ConfigureAwait(false); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             await @this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(false); | ||||
|                         } | ||||
|  | ||||
|                         return true; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         await this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(false); | ||||
|                     } | ||||
|  | ||||
|                     return true; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     if (message.Type == 0x01) | ||||
|                     { | ||||
|                         bool log = false; | ||||
|                         if (id != Id) log = true; | ||||
|  | ||||
|                         //注册ID | ||||
|                         if (Service is ITcpServiceChannel tcpService && tcpService.TryGetClient(id, out var oldClient) && oldClient != this) | ||||
|                         if (message.Type == 0x01) | ||||
|                         { | ||||
|                             Logger?.Debug($"Old socket connections with the same ID {id} will be closed"); | ||||
|                             try | ||||
|                             { | ||||
|                                 //await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false); | ||||
|                                 await oldClient.CloseAsync().ConfigureAwait(false); | ||||
|                             } | ||||
|                             catch | ||||
|                             { | ||||
|                             } | ||||
|                             try | ||||
|                             { | ||||
|                                 oldClient.Dispose(); | ||||
|                             } | ||||
|                             catch | ||||
|                             bool log = false; | ||||
|                             if (id != @this.Id) log = true; | ||||
|  | ||||
|                             //注册ID | ||||
|                             if (@this.Service is ITcpServiceChannel tcpService && tcpService.TryGetClient(id, out var oldClient) && oldClient != @this) | ||||
|                             { | ||||
|                                 @this.Logger?.Debug($"Old socket connections with the same ID {id} will be closed"); | ||||
|                                 try | ||||
|                                 { | ||||
|                                     //await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false); | ||||
|                                     await oldClient.CloseAsync().ConfigureAwait(false); | ||||
|                                 } | ||||
|                                 catch | ||||
|                                 { | ||||
|                                 } | ||||
|                                 try | ||||
|                                 { | ||||
|                                     oldClient.Dispose(); | ||||
|                                 } | ||||
|                                 catch | ||||
|                                 { | ||||
|                                 } | ||||
|                             } | ||||
|  | ||||
|                             await @this.ResetIdAsync(id, @this.ClosedToken).ConfigureAwait(false); | ||||
|  | ||||
|                             //发送成功 | ||||
|                             await @this.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), @this.ClosedToken).ConfigureAwait(false); | ||||
|                             if (log) | ||||
|                                 @this.Logger?.Info(string.Format(AppResource.DtuConnected, @this.Id)); | ||||
|                         } | ||||
|                         else if (message.Type == 0x02) | ||||
|                         { | ||||
|                             await @this.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, @this.Id, true, 0x82), @this.ClosedToken).ConfigureAwait(false); | ||||
|                             @this.Logger?.Info(string.Format(AppResource.DtuDisconnecting, @this.Id)); | ||||
|                             await Task.Delay(100).ConfigureAwait(false); | ||||
|                             await @this.CloseAsync().ConfigureAwait(false); | ||||
|                             @this.SafeDispose(); | ||||
|                         } | ||||
|  | ||||
|                         await ResetIdAsync(id, ClosedToken).ConfigureAwait(false); | ||||
|  | ||||
|                         //发送成功 | ||||
|                         await base.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), ClosedToken).ConfigureAwait(false); | ||||
|                         if (log) | ||||
|                             Logger?.Info(string.Format(AppResource.DtuConnected, Id)); | ||||
|                     } | ||||
|                     else if (message.Type == 0x02) | ||||
|                     { | ||||
|                         await base.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, Id, true, 0x82), ClosedToken).ConfigureAwait(false); | ||||
|                         Logger?.Info(string.Format(AppResource.DtuDisconnecting, Id)); | ||||
|                         await Task.Delay(100).ConfigureAwait(false); | ||||
|                         await this.CloseAsync().ConfigureAwait(false); | ||||
|                         this.SafeDispose(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #region Throw | ||||
|   | ||||
| @@ -8,6 +8,8 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using PooledAwait; | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
| using System.Net; | ||||
| using System.Runtime.CompilerServices; | ||||
| @@ -80,73 +82,78 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe | ||||
|     } | ||||
|  | ||||
|  | ||||
|     protected override async ValueTask<bool> OnUdpReceiving(UdpReceiveingEventArgs e) | ||||
|     protected override ValueTask<bool> OnUdpReceiving(UdpReceiveingEventArgs e) | ||||
|     { | ||||
|         var byteBlock = e.Memory; | ||||
|         var endPoint = e.EndPoint; | ||||
|  | ||||
|         if (!DDPAdapter.TryParseRequest(endPoint, byteBlock, out var message)) | ||||
|             return true; | ||||
|             return EasyValueTask.FromResult(true); | ||||
|  | ||||
|         if (message != null) | ||||
|         return OnUdpReceiving(this, endPoint, message); | ||||
|  | ||||
|         static async PooledValueTask<bool> OnUdpReceiving(DDPUdpSessionChannel @this, EndPoint endPoint, DDPUdpMessage message) | ||||
|         { | ||||
|             if (message.IsSuccess) | ||||
|             if (message != null) | ||||
|             { | ||||
|                 var id = $"ID={message.Id}"; | ||||
|                 if (message.Type == 0x09) | ||||
|                 if (message.IsSuccess) | ||||
|                 { | ||||
|                     if (this.DataHandlingAdapter == null) | ||||
|                     var id = $"ID={message.Id}"; | ||||
|                     if (message.Type == 0x09) | ||||
|                     { | ||||
|                         await this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, message.Content, default)).ConfigureAwait(false); | ||||
|                         if (@this.DataHandlingAdapter == null) | ||||
|                         { | ||||
|                             await @this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, message.Content, default)).ConfigureAwait(false); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             await @this.DataHandlingAdapter.ReceivedInputAsync(endPoint, message.Content).ConfigureAwait(false); | ||||
|                         } | ||||
|  | ||||
|                         return true; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         await this.DataHandlingAdapter.ReceivedInputAsync(endPoint, message.Content).ConfigureAwait(false); | ||||
|                     } | ||||
|                         if (message.Type == 0x01) | ||||
|                         { | ||||
|                             bool log = false; | ||||
|  | ||||
|                     return true; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     if (message.Type == 0x01) | ||||
|                     { | ||||
|                         bool log = false; | ||||
|                             //注册ID | ||||
|                             if (!@this.IdDict.TryAdd(endPoint, id)) | ||||
|                             { | ||||
|                                 @this.IdDict[endPoint] = id; | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 log = true; | ||||
|                             } | ||||
|                             if (!@this.EndPointDcit.TryAdd(id, endPoint)) | ||||
|                             { | ||||
|                                 @this.EndPointDcit[id] = endPoint; | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 log = true; | ||||
|                             } | ||||
|  | ||||
|                         //注册ID | ||||
|                         if (!IdDict.TryAdd(endPoint, id)) | ||||
|                         { | ||||
|                             IdDict[endPoint] = id; | ||||
|                             //发送成功 | ||||
|                             await @this.DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x81), @this.ClosedToken).ConfigureAwait(false); | ||||
|                             if (log) | ||||
|                                 @this.Logger?.Info(string.Format(AppResource.DtuConnected, id)); | ||||
|                         } | ||||
|                         else | ||||
|                         else if (message.Type == 0x02) | ||||
|                         { | ||||
|                             log = true; | ||||
|                             await @this.DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x82), @this.ClosedToken).ConfigureAwait(false); | ||||
|                             @this.Logger?.Info(string.Format(AppResource.DtuDisconnecting, id)); | ||||
|                             await Task.Delay(100).ConfigureAwait(false); | ||||
|                             @this.IdDict.TryRemove(endPoint, out _); | ||||
|                             @this.EndPointDcit.TryRemove(id, out _); | ||||
|                         } | ||||
|                         if (!EndPointDcit.TryAdd(id, endPoint)) | ||||
|                         { | ||||
|                             EndPointDcit[id] = endPoint; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             log = true; | ||||
|                         } | ||||
|  | ||||
|                         //发送成功 | ||||
|                         await DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x81), ClosedToken).ConfigureAwait(false); | ||||
|                         if (log) | ||||
|                             Logger?.Info(string.Format(AppResource.DtuConnected, id)); | ||||
|                     } | ||||
|                     else if (message.Type == 0x02) | ||||
|                     { | ||||
|                         await DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x82), ClosedToken).ConfigureAwait(false); | ||||
|                         Logger?.Info(string.Format(AppResource.DtuDisconnecting, id)); | ||||
|                         await Task.Delay(100).ConfigureAwait(false); | ||||
|                         IdDict.TryRemove(endPoint, out _); | ||||
|                         EndPointDcit.TryRemove(id, out _); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     #region Throw | ||||
|   | ||||
| @@ -8,6 +8,8 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using PooledAwait; | ||||
|  | ||||
| using ThingsGateway.Foundation.Extension.String; | ||||
|  | ||||
| using TouchSocket.SerialPorts; | ||||
| @@ -26,22 +28,31 @@ public static class ChannelOptionsExtensions | ||||
|     /// <param name="e">接收数据</param> | ||||
|     /// <param name="funcs">事件</param> | ||||
|     /// <returns></returns> | ||||
|     internal static async Task OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs) | ||||
|     internal static ValueTask OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs) | ||||
|     { | ||||
|         clientChannel.ThrowIfNull(nameof(IClientChannel)); | ||||
|         e.ThrowIfNull(nameof(ReceivedDataEventArgs)); | ||||
|         funcs.ThrowIfNull(nameof(ChannelReceivedEventHandler)); | ||||
|  | ||||
|         if (funcs.Count > 0) | ||||
|         return OnChannelReceivedEvent(clientChannel, e, funcs); | ||||
|  | ||||
|         static async PooledValueTask OnChannelReceivedEvent(IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs) | ||||
|         { | ||||
|             for (int i = 0; i < funcs.Count; i++) | ||||
|             if (funcs.Count > 0) | ||||
|             { | ||||
|                 var func = funcs[i]; | ||||
|                 if (func == null) continue; | ||||
|                 await func.Invoke(clientChannel, e, i == funcs.Count - 1).ConfigureAwait(false); | ||||
|                 if (e.Handled) | ||||
|                 for (int i = 0; i < funcs.Count; i++) | ||||
|                 { | ||||
|                     break; | ||||
|                     var func = funcs[i]; | ||||
|                     if (func == null) continue; | ||||
|                     var taskResult = func.Invoke(clientChannel, e, i == funcs.Count - 1); | ||||
|                     if (!taskResult.IsCompletedSuccessfully) | ||||
|                     { | ||||
|                         await taskResult.ConfigureAwait(false); | ||||
|                     } | ||||
|                     if (e.Handled) | ||||
|                     { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -53,7 +64,7 @@ public static class ChannelOptionsExtensions | ||||
|     /// <param name="clientChannel">通道</param> | ||||
|     /// <param name="funcs">事件</param> | ||||
|     /// <returns></returns> | ||||
|     internal static async Task OnChannelEvent(this IClientChannel clientChannel, ChannelEventHandler funcs) | ||||
|     internal static async ValueTask OnChannelEvent(this IClientChannel clientChannel, ChannelEventHandler funcs) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|   | ||||
| @@ -65,7 +65,7 @@ public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient, IC | ||||
| /// <summary> | ||||
| /// 接收事件回调类 | ||||
| /// </summary> | ||||
| public class ChannelReceivedEventHandler : List<Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> | ||||
| public class ChannelReceivedEventHandler : List<Func<IClientChannel, ReceivedDataEventArgs, bool, ValueTask>> | ||||
| { | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -30,7 +30,6 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn | ||||
|     WaitHandlePool<MessageBase> WaitHandlePool { get; } | ||||
|  | ||||
|     WaitLock GetLock(string key); | ||||
|     void LogSeted(bool logSeted); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置数据处理适配器 | ||||
|   | ||||
| @@ -80,13 +80,11 @@ public class OtherChannel : SetupConfigObject, IClientChannel | ||||
|  | ||||
|  | ||||
|  | ||||
|     private bool logSet; | ||||
|     /// <inheritdoc/> | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (!logSet && ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             logSet = true; | ||||
|             handleAdapter.Logger = log; | ||||
|         } | ||||
|     } | ||||
| @@ -96,12 +94,8 @@ public class OtherChannel : SetupConfigObject, IClientChannel | ||||
|         if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter) | ||||
|             SetAdapter(singleStreamDataHandlingAdapter); | ||||
|  | ||||
|         logSet = false; | ||||
|     } | ||||
|     public void LogSeted(bool logSeted) | ||||
|     { | ||||
|         logSet = logSeted; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置数据处理适配器。 | ||||
|     /// </summary> | ||||
| @@ -131,10 +125,10 @@ public class OtherChannel : SetupConfigObject, IClientChannel | ||||
|         m_dataHandlingAdapter = adapter; | ||||
|     } | ||||
|  | ||||
|     private Task PrivateHandleReceivedData(ReadOnlyMemory<byte> byteBlock, IRequestInfo requestInfo) | ||||
|     private async Task PrivateHandleReceivedData(ReadOnlyMemory<byte> byteBlock, IRequestInfo requestInfo) | ||||
|     { | ||||
|         LastReceivedTime = DateTime.Now; | ||||
|         return this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived); | ||||
|         await this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -8,6 +8,8 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using PooledAwait; | ||||
|  | ||||
| using System.Text; | ||||
|  | ||||
| using ThingsGateway.Foundation.Extension.String; | ||||
| @@ -59,64 +61,71 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin | ||||
|     public bool DtuIdHex { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e) | ||||
|     public Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e) | ||||
|     { | ||||
|         var len = HeartbeatByte.Length; | ||||
|         if (client is TcpSessionClientChannel socket && socket.Service is ITcpServiceChannel tcpServiceChannel) | ||||
|         return OnTcpReceiving(this, client, e); | ||||
|  | ||||
|  | ||||
|         static async PooledTask OnTcpReceiving(DtuPlugin @this, ITcpSession client, BytesReaderEventArgs e) | ||||
|         { | ||||
|             if (!socket.Id.StartsWith("ID=")) | ||||
|             var len = @this.HeartbeatByte.Length; | ||||
|             if (client is TcpSessionClientChannel socket && socket.Service is ITcpServiceChannel tcpServiceChannel) | ||||
|             { | ||||
|                 var id = DtuIdHex ? $"ID={e.Reader.ToHexString()}" : $"ID={e.Reader.TotalSequence.ToString(Encoding.UTF8)}"; | ||||
|                 if (tcpServiceChannel.TryGetClient(id, out var oldClient)) | ||||
|                 if (!socket.Id.StartsWith("ID=")) | ||||
|                 { | ||||
|                     var id = @this.DtuIdHex ? $"ID={e.Reader.ToHexString()}" : $"ID={e.Reader.TotalSequence.ToString(Encoding.UTF8)}"; | ||||
|                     if (tcpServiceChannel.TryGetClient(id, out var oldClient)) | ||||
|                     { | ||||
|                         try | ||||
|                         { | ||||
|                             await oldClient.CloseAsync().ConfigureAwait(false); | ||||
|                             oldClient.Dispose(); | ||||
|                         } | ||||
|                         catch | ||||
|                         { | ||||
|                         } | ||||
|                     } | ||||
|                     await socket.ResetIdAsync(id, client.ClosedToken).ConfigureAwait(false); | ||||
|                     client.Logger?.Info(string.Format(AppResource.DtuConnected, id)); | ||||
|                     e.Reader.Advance((int)e.Reader.BytesRemaining); | ||||
|                     e.Handled = true; | ||||
|                 } | ||||
|  | ||||
|                 if (!socket.Service.ClientExists(socket.Id)) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         await oldClient.CloseAsync().ConfigureAwait(false); | ||||
|                         oldClient.Dispose(); | ||||
|                         await socket.CloseAsync().ConfigureAwait(false); | ||||
|                         socket.Dispose(); | ||||
|                     } | ||||
|                     catch | ||||
|                     { | ||||
|                     } | ||||
|                 } | ||||
|                 await socket.ResetIdAsync(id, client.ClosedToken).ConfigureAwait(false); | ||||
|                 client.Logger?.Info(string.Format(AppResource.DtuConnected, id)); | ||||
|                 e.Reader.Advance((int)e.Reader.BytesRemaining); | ||||
|                 e.Handled = true; | ||||
|             } | ||||
|  | ||||
|             if (!socket.Service.ClientExists(socket.Id)) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     await socket.CloseAsync().ConfigureAwait(false); | ||||
|                     socket.Dispose(); | ||||
|                 } | ||||
|                 catch | ||||
|                 { | ||||
|                     await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。 | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。 | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if (len > 0) | ||||
|             { | ||||
|                 if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, (int)Math.Min(len, e.Reader.BytesRemaining + e.Reader.BytesRead)).First.Span)) | ||||
|                 if (len > 0) | ||||
|                 { | ||||
|                     if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200)) | ||||
|                     if (@this.HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, (int)Math.Min(len, e.Reader.BytesRemaining + e.Reader.BytesRead)).First.Span)) | ||||
|                     { | ||||
|                         await Task.Delay(200, client.ClosedToken).ConfigureAwait(false); | ||||
|                         if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200)) | ||||
|                         { | ||||
|                             await Task.Delay(200, client.ClosedToken).ConfigureAwait(false); | ||||
|                         } | ||||
|                         //回应心跳包 | ||||
|                         await socket.SendAsync(@this.HeartbeatByte, socket.ClosedToken).ConfigureAwait(false); | ||||
|                         e.Reader.Advance((int)Math.Min(len, e.Reader.BytesRemaining)); | ||||
|                         e.Handled = true; | ||||
|                         if (socket.Logger?.LogLevel <= LogLevel.Trace) | ||||
|                             socket.Logger?.Trace($"{socket}- Heartbeat"); | ||||
|                     } | ||||
|                     //回应心跳包 | ||||
|                     await socket.SendAsync(HeartbeatByte, socket.ClosedToken).ConfigureAwait(false); | ||||
|                     e.Reader.Advance((int)Math.Min(len, e.Reader.BytesRemaining)); | ||||
|                     e.Handled = true; | ||||
|                     if (socket.Logger?.LogLevel <= LogLevel.Trace) | ||||
|                         socket.Logger?.Trace($"{socket}- Heartbeat"); | ||||
|                 } | ||||
|             } | ||||
|             await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。 | ||||
|             return; | ||||
|         } | ||||
|         await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。 | ||||
|     } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -167,14 +167,14 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi | ||||
|         await e.InvokeNext().ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e) | ||||
|     public Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e) | ||||
|     { | ||||
|         if (client is ITcpSessionClient) | ||||
|         { | ||||
|             return;//此处可判断,如果为服务器,则不用使用心跳。 | ||||
|             return Task.CompletedTask;//此处可判断,如果为服务器,则不用使用心跳。 | ||||
|         } | ||||
|  | ||||
|         if (DtuId.IsNullOrWhiteSpace()) return; | ||||
|         if (DtuId.IsNullOrWhiteSpace()) return Task.CompletedTask; | ||||
|  | ||||
|         if (client is ITcpClient tcpClient) | ||||
|         { | ||||
| @@ -187,8 +187,9 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi | ||||
|                     e.Handled = true; | ||||
|                 } | ||||
|             } | ||||
|             await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。 | ||||
|             return e.InvokeNext();//如果本插件无法处理当前数据,请将数据转至下一个插件。 | ||||
|         } | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -34,25 +34,29 @@ public static class PluginUtil | ||||
|  | ||||
|             if (channelOptions.ChannelType == ChannelTypeEnum.TcpClient) | ||||
|             { | ||||
|                 action += a => a.UseReconnection<IClientChannel>().SetActionForCheck((channel, failCount) => | ||||
|                 action += a => a.UseReconnection<IClientChannel>(a => | ||||
|                 { | ||||
|                     if (channel.Online) | ||||
|                     a.CheckAction = (channel, failCount) => | ||||
|                     { | ||||
|                         return Task.FromResult(ConnectionCheckResult.Alive); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         if (failCount > 1) | ||||
|                         if (channel.Online) | ||||
|                         { | ||||
|                             return Task.FromResult(ConnectionCheckResult.Dead); | ||||
|                             return Task.FromResult(ConnectionCheckResult.Alive); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             if (failCount > 1) | ||||
|                             { | ||||
|                                 return Task.FromResult(ConnectionCheckResult.Dead); | ||||
|                             } | ||||
|                             return Task.FromResult(ConnectionCheckResult.Skip); | ||||
|                         } | ||||
|                         return Task.FromResult(ConnectionCheckResult.Skip); | ||||
|                     } | ||||
|  | ||||
|  | ||||
|                 }) | ||||
|                 .SetPollingTick(TimeSpan.FromSeconds(5) | ||||
|                     }; | ||||
|                     a.PollingInterval = TimeSpan.FromSeconds(5); | ||||
|  | ||||
|  | ||||
|                 } | ||||
|                 ); | ||||
|             } | ||||
|             return action; | ||||
| @@ -93,13 +97,15 @@ public static class PluginUtil | ||||
|         { | ||||
|             action += a => | ||||
|             { | ||||
|                 a.UseTcpSessionCheckClear() | ||||
|         .SetCheckClearType(CheckClearType.All) | ||||
|         .SetTick(TimeSpan.FromMilliseconds(channelOptions.CheckClearTime)) | ||||
|         .SetOnClose((c, t) => | ||||
|         { | ||||
|             return c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout"); | ||||
|         }); | ||||
|                 a.UseTcpSessionCheckClear(options => | ||||
|                 { | ||||
|                     options.CheckClearType = CheckClearType.All; | ||||
|                     options.Tick = TimeSpan.FromMilliseconds(channelOptions.CheckClearTime); | ||||
|                     options.OnClose = (c, t) => | ||||
|                     { | ||||
|                         return c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout"); | ||||
|                     }; | ||||
|                 }); | ||||
|             }; | ||||
|         } | ||||
|         return action; | ||||
|   | ||||
| @@ -52,13 +52,11 @@ public class SerialPortChannel : SerialPortClient, IClientChannel | ||||
|     /// <inheritdoc/> | ||||
|     public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter; | ||||
|  | ||||
|     private bool logSet; | ||||
|     /// <inheritdoc/> | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (!logSet && ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             logSet = true; | ||||
|             handleAdapter.Logger = log; | ||||
|         } | ||||
|     } | ||||
| @@ -68,13 +66,9 @@ public class SerialPortChannel : SerialPortClient, IClientChannel | ||||
|         if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter) | ||||
|             SetAdapter(singleStreamDataHandlingAdapter); | ||||
|  | ||||
|         logSet = false; | ||||
|     } | ||||
|  | ||||
|     public void LogSeted(bool logSeted) | ||||
|     { | ||||
|         logSet = logSeted; | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|   | ||||
| @@ -34,12 +34,10 @@ public class TcpClientChannel : TcpClient, IClientChannel | ||||
|         WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign); | ||||
|         pool?.CancelAll(); | ||||
|     } | ||||
|     private bool logSet; | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             logSet = true; | ||||
|             handleAdapter.Logger = log; | ||||
|         } | ||||
|     } | ||||
| @@ -49,12 +47,8 @@ public class TcpClientChannel : TcpClient, IClientChannel | ||||
|         if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter) | ||||
|             SetAdapter(singleStreamDataHandlingAdapter); | ||||
|  | ||||
|         logSet = false; | ||||
|     } | ||||
|     public void LogSeted(bool logSeted) | ||||
|     { | ||||
|         logSet = logSeted; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public ChannelReceivedEventHandler ChannelReceived { get; } = new(); | ||||
|  | ||||
|   | ||||
| @@ -25,13 +25,11 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel | ||||
|     public TcpSessionClientChannel() | ||||
|     { | ||||
|     } | ||||
|     private bool logSet; | ||||
|     /// <inheritdoc/> | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             logSet = true; | ||||
|             handleAdapter.Logger = log; | ||||
|         } | ||||
|     } | ||||
| @@ -41,12 +39,8 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel | ||||
|         if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter) | ||||
|             SetAdapter(singleStreamDataHandlingAdapter); | ||||
|  | ||||
|         logSet = false; | ||||
|     } | ||||
|     public void LogSeted(bool logSeted) | ||||
|     { | ||||
|         logSet = logSeted; | ||||
|     } | ||||
|  | ||||
|     public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1) | ||||
|     { | ||||
|         var pool = WaitHandlePool; | ||||
|   | ||||
| @@ -30,27 +30,21 @@ public class UdpSessionChannel : UdpSession, IClientChannel | ||||
|         ResetSign(); | ||||
|     } | ||||
|     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; | ||||
|     private bool logSet; | ||||
|     /// <inheritdoc/> | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             logSet = true; | ||||
|             handleAdapter.Logger = log; | ||||
|         } | ||||
|     } | ||||
|     public void LogSeted(bool logSeted) | ||||
|     { | ||||
|         logSet = logSeted; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public void SetDataHandlingAdapter(DataHandlingAdapter adapter) | ||||
|     { | ||||
|         if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter) | ||||
|             SetAdapter(udpDataHandlingAdapter); | ||||
|  | ||||
|         logSet = false; | ||||
|     } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,8 @@ | ||||
|  | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| using PooledAwait; | ||||
|  | ||||
| using System.Net; | ||||
|  | ||||
| using ThingsGateway.Foundation.Extension.Generic; | ||||
| @@ -308,7 +310,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|     /// <summary> | ||||
|     /// 接收,非主动发送的情况,重写实现非主从并发通讯协议,如果通道存在其他设备并且不希望其他设备处理时,设置<see cref="TouchSocketEventArgs.Handled"/> 为true | ||||
|     /// </summary> | ||||
|     protected virtual Task ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last) | ||||
|     protected virtual ValueTask ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last) | ||||
|     { | ||||
|         if (e.RequestInfo is MessageBase response) | ||||
|         { | ||||
| @@ -325,25 +327,33 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return EasyTask.CompletedTask; | ||||
|         return EasyValueTask.CompletedTask; | ||||
|     } | ||||
|     public bool AutoConnect { get; protected set; } = true; | ||||
|     /// <inheritdoc/> | ||||
|     private async Task SendAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken token = default) | ||||
|     private Task SendAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken token = default) | ||||
|     { | ||||
|  | ||||
|         if (SendDelayTime != 0) | ||||
|             await Task.Delay(SendDelayTime, token).ConfigureAwait(false); | ||||
|  | ||||
|         if (channel is IDtuUdpSessionChannel udpSession) | ||||
|         if(!channel.Online) | ||||
|         { | ||||
|             EndPoint? endPoint = GetUdpEndpoint(); | ||||
|             await udpSession.SendAsync(endPoint, sendMessage, token).ConfigureAwait(false); | ||||
|  | ||||
|             throw new InvalidOperationException("Channel is offline"); | ||||
|         } | ||||
|         else | ||||
|         return SendAsync(this, sendMessage, channel, token); | ||||
|  | ||||
|         static async PooledTask SendAsync(DeviceBase @this, ISendMessage sendMessage, IClientChannel channel, CancellationToken token) | ||||
|         { | ||||
|             await channel.SendAsync(sendMessage, token).ConfigureAwait(false); | ||||
|             if (@this.SendDelayTime != 0) | ||||
|                 await Task.Delay(@this.SendDelayTime, token).ConfigureAwait(false); | ||||
|  | ||||
|             if (channel is IDtuUdpSessionChannel udpSession) | ||||
|             { | ||||
|                 EndPoint? endPoint = @this.GetUdpEndpoint(); | ||||
|                 await udpSession.SendAsync(endPoint, sendMessage, token).ConfigureAwait(false); | ||||
|  | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 await channel.SendAsync(sendMessage, token).ConfigureAwait(false); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
| @@ -363,59 +373,69 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|  | ||||
|     private WaitLock connectWaitLock = new(nameof(DeviceBase)); | ||||
|  | ||||
|     public async ValueTask ConnectAsync(CancellationToken token) | ||||
|     public ValueTask ConnectAsync(CancellationToken token) | ||||
|     { | ||||
|         if (AutoConnect && Channel != null && Channel?.Online != true) | ||||
|         return ConnectAsync(this, token); | ||||
|  | ||||
|         static async PooledValueTask ConnectAsync(DeviceBase @this, CancellationToken token) | ||||
|         { | ||||
|             try | ||||
|             if (@this.AutoConnect && @this.Channel != null && @this.Channel?.Online != true) | ||||
|             { | ||||
|                 await connectWaitLock.WaitAsync(token).ConfigureAwait(false); | ||||
|                 if (AutoConnect && Channel != null && Channel?.Online != true) | ||||
|                 try | ||||
|                 { | ||||
|                     if (Channel.PluginManager == null) | ||||
|                         await Channel.SetupAsync(Channel.Config.Clone()).ConfigureAwait(false); | ||||
|                     await Channel.CloseAsync().ConfigureAwait(false); | ||||
|                     using var ctsTime = new CancellationTokenSource(Channel.ChannelOptions.ConnectTimeout); | ||||
|                     using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, token); | ||||
|                     await Channel.ConnectAsync(cts.Token).ConfigureAwait(false); | ||||
|                     await @this.connectWaitLock.WaitAsync(token).ConfigureAwait(false); | ||||
|                     if (@this.AutoConnect && @this.Channel != null && @this.Channel?.Online != true) | ||||
|                     { | ||||
|                         if (@this.Channel.PluginManager == null) | ||||
|                             await @this.Channel.SetupAsync(@this.Channel.Config.Clone()).ConfigureAwait(false); | ||||
|                         await @this.Channel.CloseAsync().ConfigureAwait(false); | ||||
|                         using var ctsTime = new CancellationTokenSource(@this.Channel.ChannelOptions.ConnectTimeout); | ||||
|                         using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, token); | ||||
|                         await @this.Channel.ConnectAsync(cts.Token).ConfigureAwait(false); | ||||
|                     } | ||||
|                 } | ||||
|                 finally | ||||
|                 { | ||||
|                     @this.connectWaitLock.Release(); | ||||
|                 } | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 connectWaitLock.Release(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public virtual async ValueTask<OperResult> SendAsync(ISendMessage sendMessage, CancellationToken cancellationToken) | ||||
|     public virtual ValueTask<OperResult> SendAsync(ISendMessage sendMessage, CancellationToken cancellationToken) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var channelResult = GetChannel(); | ||||
|             if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult); | ||||
|             WaitLock? waitLock = GetWaitLock(channelResult.Content); | ||||
|         return SendAsync(this, sendMessage, cancellationToken); | ||||
|  | ||||
|         static async PooledValueTask<OperResult> SendAsync(DeviceBase @this, ISendMessage sendMessage, CancellationToken cancellationToken) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await BeforeSendAsync(channelResult.Content, cancellationToken).ConfigureAwait(false); | ||||
|                 var channelResult = @this.GetChannel(); | ||||
|                 if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult); | ||||
|                 WaitLock? waitLock = @this.GetWaitLock(channelResult.Content); | ||||
|  | ||||
|                 await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||
|                 channelResult.Content.SetDataHandlingAdapterLogger(Logger); | ||||
|                 try | ||||
|                 { | ||||
|                     await @this.BeforeSendAsync(channelResult.Content, cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|                     await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||
|                     channelResult.Content.SetDataHandlingAdapterLogger(@this.Logger); | ||||
|  | ||||
|  | ||||
|                 await SendAsync(sendMessage, channelResult.Content, cancellationToken).ConfigureAwait(false); | ||||
|                 return OperResult.Success; | ||||
|                     await @this.SendAsync(sendMessage, channelResult.Content, cancellationToken).ConfigureAwait(false); | ||||
|                     return OperResult.Success; | ||||
|                 } | ||||
|                 finally | ||||
|                 { | ||||
|                     waitLock.Release(); | ||||
|                 } | ||||
|             } | ||||
|             finally | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 waitLock.Release(); | ||||
|                 return new(ex); | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
| @@ -496,16 +516,21 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public virtual async ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken = default) | ||||
|     public virtual ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         try | ||||
|         return SendThenReturn(this, sendMessage, channel, cancellationToken); | ||||
|  | ||||
|         static async PooledValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturn(DeviceBase @this, ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken) | ||||
|         { | ||||
|             var result = await SendThenReturnMessageAsync(sendMessage, channel, cancellationToken).ConfigureAwait(false); | ||||
|             return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content }; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new(ex); | ||||
|             try | ||||
|             { | ||||
|                 var result = await @this.SendThenReturnMessageAsync(sendMessage, channel, cancellationToken).ConfigureAwait(false); | ||||
|                 return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content }; | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 return new(ex); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -523,48 +548,83 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|         return GetResponsedDataAsync(command, clientChannel, Timeout, cancellationToken); | ||||
|     } | ||||
|  | ||||
|     private ObjectPool<ReusableCancellationTokenSource> _reusableTimeouts = new(); | ||||
|     private ObjectPoolLock<ReusableCancellationTokenSource> _reusableTimeouts = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 发送并等待数据 | ||||
|     /// </summary> | ||||
|     protected async ValueTask<MessageBase> GetResponsedDataAsync( | ||||
|     protected ValueTask<MessageBase> GetResponsedDataAsync( | ||||
|         ISendMessage command, | ||||
|         IClientChannel clientChannel, | ||||
|         int timeout = 3000, | ||||
|         CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         var waitData = clientChannel.WaitHandlePool.GetWaitDataAsync(out var sign); | ||||
|         command.Sign = sign; | ||||
|         WaitLock? waitLock = null; | ||||
|         return GetResponsedDataAsync(this, command, clientChannel, timeout, cancellationToken); | ||||
|  | ||||
|         try | ||||
|         static async PooledValueTask<MessageBase> GetResponsedDataAsync(DeviceBase @this, ISendMessage command, IClientChannel clientChannel, int timeout, CancellationToken cancellationToken) | ||||
|         { | ||||
|             await BeforeSendAsync(clientChannel, cancellationToken).ConfigureAwait(false); | ||||
|             var waitData = clientChannel.WaitHandlePool.GetWaitDataAsync(out var sign); | ||||
|             command.Sign = sign; | ||||
|             WaitLock? waitLock = null; | ||||
|  | ||||
|             waitLock = GetWaitLock(clientChannel); | ||||
|  | ||||
|             await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|             clientChannel.SetDataHandlingAdapterLogger(Logger); | ||||
|  | ||||
|             await SendAsync(command, clientChannel, cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|             if (waitData.Status == WaitDataStatus.Success) | ||||
|                 return waitData.CompletedData; | ||||
|  | ||||
|             var reusableTimeout = _reusableTimeouts.Get(); | ||||
|             var cts = reusableTimeout.GetTokenSource(timeout, cancellationToken, Channel.ClosedToken); | ||||
|             try | ||||
|             { | ||||
|                 await @this.BeforeSendAsync(clientChannel, cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|                 await waitData.WaitAsync(cts.Token).ConfigureAwait(false); | ||||
|                 waitLock = @this.GetWaitLock(clientChannel); | ||||
|  | ||||
|             } | ||||
|             catch (OperationCanceledException) | ||||
|             { | ||||
|                 return reusableTimeout.TimeoutStatus | ||||
|                     ? new MessageBase(new TimeoutException()) { ErrorMessage = $"Timeout, sign: {sign}" } | ||||
|                     : new MessageBase(new OperationCanceledException()); | ||||
|                 await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|                 clientChannel.SetDataHandlingAdapterLogger(@this.Logger); | ||||
|  | ||||
|                 await @this.SendAsync(command, clientChannel, cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|                 if (waitData.Status == WaitDataStatus.Success) | ||||
|                     return waitData.CompletedData; | ||||
|  | ||||
|                 var reusableTimeout = @this._reusableTimeouts.Get(); | ||||
|                 try | ||||
|                 { | ||||
|  | ||||
|                     var ctsToken = reusableTimeout.GetTokenSource(timeout, cancellationToken, @this.Channel.ClosedToken); | ||||
|                     await waitData.WaitAsync(ctsToken).ConfigureAwait(false); | ||||
|  | ||||
|                 } | ||||
|                 catch (OperationCanceledException) | ||||
|                 { | ||||
|                     return reusableTimeout.TimeoutStatus | ||||
|                         ? new MessageBase(new TimeoutException()) { ErrorMessage = $"Timeout, sign: {sign}" } | ||||
|                         : new MessageBase(new OperationCanceledException()); | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     return new MessageBase(ex); | ||||
|                 } | ||||
|                 finally | ||||
|                 { | ||||
|                     reusableTimeout.Set(); | ||||
|                     @this._reusableTimeouts.Return(reusableTimeout); | ||||
|                 } | ||||
|  | ||||
|                 if (waitData.Status == WaitDataStatus.Success) | ||||
|                 { | ||||
|                     return waitData.CompletedData; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     var operResult = waitData.Check(reusableTimeout.TimeoutStatus); | ||||
|                     if (waitData.CompletedData != null) | ||||
|                     { | ||||
|                         waitData.CompletedData.ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}"; | ||||
|                         return waitData.CompletedData; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         return new MessageBase(new OperationCanceledException()); | ||||
|                     } | ||||
|  | ||||
|                     //return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" }; | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
| @@ -572,39 +632,10 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 reusableTimeout.Set(); | ||||
|                 _reusableTimeouts.Return(reusableTimeout); | ||||
|             } | ||||
|                 waitLock?.Release(); | ||||
|                 waitData?.SafeDispose(); | ||||
|  | ||||
|             if (waitData.Status == WaitDataStatus.Success) | ||||
|             { | ||||
|                 return waitData.CompletedData; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 var operResult = waitData.Check(reusableTimeout.TimeoutStatus); | ||||
|                 if(waitData.CompletedData!=null) | ||||
|                 { | ||||
|                     waitData.CompletedData.ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}"; | ||||
|                     return waitData.CompletedData; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     return new MessageBase(new OperationCanceledException()); | ||||
|                 } | ||||
|  | ||||
|                 //return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" }; | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new MessageBase(ex); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             waitLock?.Release(); | ||||
|             waitData?.SafeDispose(); | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -646,54 +677,59 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public virtual async ValueTask<OperResult> WriteJTokenAsync(string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken = default) | ||||
|     public virtual ValueTask<OperResult> WriteJTokenAsync(string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         try | ||||
|         return WriteJTokenAsync(this, address, value, dataType, cancellationToken); | ||||
|  | ||||
|         static async PooledValueTask<OperResult> WriteJTokenAsync(DeviceBase @this, string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken) | ||||
|         { | ||||
|             var bitConverter = ThingsGatewayBitConverter.GetTransByAddress(address); | ||||
|             if (value is JArray jArray) | ||||
|             try | ||||
|             { | ||||
|                 return dataType switch | ||||
|                 var bitConverter = @this.ThingsGatewayBitConverter.GetTransByAddress(address); | ||||
|                 if (value is JArray jArray) | ||||
|                 { | ||||
|                     DataTypeEnum.String => await WriteAsync(address, jArray.ToObject<String[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Boolean => await WriteAsync(address, jArray.ToObject<Boolean[]>().AsMemory(), cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Byte => await WriteAsync(address, jArray.ToObject<Byte[]>().AsMemory(), dataType, cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Int16 => await WriteAsync(address, jArray.ToObject<Int16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.UInt16 => await WriteAsync(address, jArray.ToObject<UInt16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Int32 => await WriteAsync(address, jArray.ToObject<Int32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.UInt32 => await WriteAsync(address, jArray.ToObject<UInt32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Int64 => await WriteAsync(address, jArray.ToObject<Int64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.UInt64 => await WriteAsync(address, jArray.ToObject<UInt64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Float => await WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Double => await WriteAsync(address, jArray.ToObject<Double[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Decimal => await WriteAsync(address, jArray.ToObject<Decimal[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                     _ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)), | ||||
|                 }; | ||||
|                     return dataType switch | ||||
|                     { | ||||
|                         DataTypeEnum.String => await @this.WriteAsync(address, jArray.ToObject<String[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Boolean => await @this.WriteAsync(address, jArray.ToObject<Boolean[]>().AsMemory(), cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Byte => await @this.WriteAsync(address, jArray.ToObject<Byte[]>().AsMemory(), dataType, cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Int16 => await @this.WriteAsync(address, jArray.ToObject<Int16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.UInt16 => await @this.WriteAsync(address, jArray.ToObject<UInt16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Int32 => await @this.WriteAsync(address, jArray.ToObject<Int32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.UInt32 => await @this.WriteAsync(address, jArray.ToObject<UInt32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Int64 => await @this.WriteAsync(address, jArray.ToObject<Int64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.UInt64 => await @this.WriteAsync(address, jArray.ToObject<UInt64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Float => await @this.WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Double => await @this.WriteAsync(address, jArray.ToObject<Double[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Decimal => await @this.WriteAsync(address, jArray.ToObject<Decimal[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||
|                         _ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)), | ||||
|                     }; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     return dataType switch | ||||
|                     { | ||||
|                         DataTypeEnum.String => await @this.WriteAsync(address, value.ToObject<String>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Boolean => await @this.WriteAsync(address, value.ToObject<Boolean>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Byte => await @this.WriteAsync(address, value.ToObject<Byte>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Int16 => await @this.WriteAsync(address, value.ToObject<Int16>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.UInt16 => await @this.WriteAsync(address, value.ToObject<UInt16>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Int32 => await @this.WriteAsync(address, value.ToObject<Int32>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.UInt32 => await @this.WriteAsync(address, value.ToObject<UInt32>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Int64 => await @this.WriteAsync(address, value.ToObject<Int64>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.UInt64 => await @this.WriteAsync(address, value.ToObject<UInt64>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Float => await @this.WriteAsync(address, value.ToObject<Single>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Double => await @this.WriteAsync(address, value.ToObject<Double>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                         DataTypeEnum.Decimal => await @this.WriteAsync(address, value.ToObject<Decimal>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                         _ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)), | ||||
|                     }; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 return dataType switch | ||||
|                 { | ||||
|                     DataTypeEnum.String => await WriteAsync(address, value.ToObject<String>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Boolean => await WriteAsync(address, value.ToObject<Boolean>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Byte => await WriteAsync(address, value.ToObject<Byte>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Int16 => await WriteAsync(address, value.ToObject<Int16>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.UInt16 => await WriteAsync(address, value.ToObject<UInt16>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Int32 => await WriteAsync(address, value.ToObject<Int32>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.UInt32 => await WriteAsync(address, value.ToObject<UInt32>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Int64 => await WriteAsync(address, value.ToObject<Int64>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.UInt64 => await WriteAsync(address, value.ToObject<UInt64>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Float => await WriteAsync(address, value.ToObject<Single>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Double => await WriteAsync(address, value.ToObject<Double>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                     DataTypeEnum.Decimal => await WriteAsync(address, value.ToObject<Decimal>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||
|                     _ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)), | ||||
|                 }; | ||||
|                 return new OperResult(ex); | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new OperResult(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #endregion 动态类型读写 | ||||
| @@ -1028,10 +1064,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|                 } | ||||
|  | ||||
|                 Channel.Collects.Remove(this); | ||||
|                 if (Channel is IClientChannel clientChannel) | ||||
|                 { | ||||
|                     clientChannel.LogSeted(false); | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|         } | ||||
| @@ -1086,10 +1118,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|  | ||||
|             Channel.Collects.Remove(this); | ||||
|  | ||||
|             if (Channel is IClientChannel clientChannel) | ||||
|             { | ||||
|                 clientChannel.LogSeted(false); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -102,33 +102,77 @@ public static partial class DeviceExtension | ||||
|     public static OperResult PraseStructContent<T>(this IEnumerable<T> variables, IDevice device, ReadOnlySpan<byte> buffer, bool exWhenAny) where T : IVariable | ||||
|     { | ||||
|         var time = DateTime.Now; | ||||
|         var result = OperResult.Success; | ||||
|         foreach (var variable in variables) | ||||
|         if (variables is IList<T> collection) | ||||
|         { | ||||
|             IThingsGatewayBitConverter byteConverter = variable.ThingsGatewayBitConverter; | ||||
|             var dataType = variable.DataType; | ||||
|             int index = variable.Index; | ||||
|             try | ||||
|             { | ||||
|                 var changed = byteConverter.GetChangedDataFormBytes(device, variable.RegisterAddress, buffer, index, dataType, variable.ArrayLength ?? 1, variable.RawValue, out var data); | ||||
|                 if (changed) | ||||
|                 { | ||||
|                     result = variable.SetValue(data, time); | ||||
|                     if (exWhenAny) | ||||
|                         if (!result.IsSuccess) | ||||
|                             return result; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     variable.SetNoChangedValue(time); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 return new OperResult($"Error parsing byte array, address: {variable.RegisterAddress}, array length: {buffer.Length}, index: {index}, type: {dataType}", ex); | ||||
|             } | ||||
|             return PraseCollection(collection, device, buffer, exWhenAny, time); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return PraseEnumerable(variables, device, buffer, exWhenAny, time); | ||||
|  | ||||
|         } | ||||
|         static OperResult PraseEnumerable(IEnumerable<T> variables, IDevice device, ReadOnlySpan<byte> buffer, bool exWhenAny, DateTime time) | ||||
|         { | ||||
|             foreach (var variable in variables) | ||||
|             { | ||||
|                 IThingsGatewayBitConverter byteConverter = variable.ThingsGatewayBitConverter; | ||||
|                 var dataType = variable.DataType; | ||||
|                 int index = variable.Index; | ||||
|                 try | ||||
|                 { | ||||
|                     var changed = byteConverter.GetChangedDataFormBytes(device, variable.RegisterAddress, buffer, index, dataType, variable.ArrayLength ?? 1, variable.RawValue, out var data); | ||||
|                     if (changed) | ||||
|                     { | ||||
|                         var result = variable.SetValue(data, time); | ||||
|                         if (exWhenAny) | ||||
|                             if (!result.IsSuccess) | ||||
|                                 return result; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         variable.SetNoChangedValue(time); | ||||
|                     } | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     return new OperResult($"Error parsing byte array, address: {variable.RegisterAddress}, array length: {buffer.Length}, index: {index}, type: {dataType}", ex); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return OperResult.Success; | ||||
|         } | ||||
|         static OperResult PraseCollection(IList<T> variables, IDevice device, ReadOnlySpan<byte> buffer, bool exWhenAny, DateTime time) | ||||
|         { | ||||
|             for (int i = 0; i < variables.Count; i++) | ||||
|             { | ||||
|                 var variable = variables[i]; | ||||
|  | ||||
|                 IThingsGatewayBitConverter byteConverter = variable.ThingsGatewayBitConverter; | ||||
|                 var dataType = variable.DataType; | ||||
|                 int index = variable.Index; | ||||
|                 try | ||||
|                 { | ||||
|                     var changed = byteConverter.GetChangedDataFormBytes(device, variable.RegisterAddress, buffer, index, dataType, variable.ArrayLength ?? 1, variable.RawValue, out var data); | ||||
|                     if (changed) | ||||
|                     { | ||||
|                         var result = variable.SetValue(data, time); | ||||
|                         if (exWhenAny) | ||||
|                             if (!result.IsSuccess) | ||||
|                                 return result; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         variable.SetNoChangedValue(time); | ||||
|                     } | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     return new OperResult($"Error parsing byte array, address: {variable.RegisterAddress}, array length: {buffer.Length}, index: {index}, type: {dataType}", ex); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return OperResult.Success; | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -128,7 +128,6 @@ public class TextFileLogger : ThingsGateway.NewLife.Log.TextFileLog, TouchSocket | ||||
|         // 推入队列 | ||||
|         Enqueue(stringBuilder.ToString()); | ||||
|  | ||||
|         WriteLog(); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|   | ||||
| @@ -43,6 +43,16 @@ public struct OperResult<T> : IOperResult<T> | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 从另一个操作对象中赋值信息 | ||||
|     /// </summary> | ||||
|     public OperResult(OperResult operResult) | ||||
|     { | ||||
|         OperCode = operResult.OperCode; | ||||
|         ErrorMessage = operResult.ErrorMessage; | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 传入错误信息 | ||||
| @@ -129,8 +139,15 @@ public struct OperResult<T> : IOperResult<T> | ||||
|     /// <param name="operResult"></param> | ||||
|     public static implicit operator OperResult(OperResult<T> operResult) | ||||
|     { | ||||
|         return new OperResult(operResult); | ||||
|         return new OperResult | ||||
|         { | ||||
|             OperCode = operResult.OperCode, | ||||
|             ErrorMessage = operResult.ErrorMessage, | ||||
|             Exception = operResult.Exception, | ||||
|             ErrorType = operResult.ErrorType | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| /// <inheritdoc/> | ||||
| @@ -162,7 +179,13 @@ public struct OperResult<T, T2> : IOperResult<T, T2> | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|  | ||||
|     public OperResult(OperResult operResult) | ||||
|     { | ||||
|         OperCode = operResult.OperCode; | ||||
|         ErrorMessage = operResult.ErrorMessage; | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 传入错误信息 | ||||
|     /// </summary> | ||||
| @@ -275,7 +298,13 @@ public struct OperResult<T, T2, T3> : IOperResult<T, T2, T3> | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|  | ||||
|     public OperResult(OperResult operResult) | ||||
|     { | ||||
|         OperCode = operResult.OperCode; | ||||
|         ErrorMessage = operResult.ErrorMessage; | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 传入错误信息 | ||||
|     /// </summary> | ||||
| @@ -389,7 +418,13 @@ public struct OperResult : IOperResult | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|  | ||||
|     public OperResult(OperResult operResult) | ||||
|     { | ||||
|         OperCode = operResult.OperCode; | ||||
|         ErrorMessage = operResult.ErrorMessage; | ||||
|         Exception = operResult.Exception; | ||||
|         ErrorType = operResult.ErrorType; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 传入错误信息 | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -8,6 +8,8 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using PooledAwait; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
| @@ -28,22 +30,26 @@ public class AsyncReadWriteLock : IAsyncDisposable | ||||
|     /// <summary> | ||||
|     /// 获取读锁,支持多个线程并发读取,但写入时会阻止所有读取。 | ||||
|     /// </summary> | ||||
|     public async ValueTask<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken) | ||||
|     public ValueTask<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         return ReaderLockAsync(this, cancellationToken); | ||||
|  | ||||
|         if (Interlocked.Read(ref _writerCount) > 0) | ||||
|         static async PooledValueTask<CancellationToken> ReaderLockAsync(AsyncReadWriteLock @this, CancellationToken cancellationToken) | ||||
|         { | ||||
|             Interlocked.Increment(ref _readerCount); | ||||
|             if (Interlocked.Read(ref @this._writerCount) > 0) | ||||
|             { | ||||
|                 Interlocked.Increment(ref @this._readerCount); | ||||
|  | ||||
|  | ||||
|  | ||||
|             // 第一个读者需要获取写入锁,防止写操作 | ||||
|             await _readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false); | ||||
|                 // 第一个读者需要获取写入锁,防止写操作 | ||||
|                 await @this._readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|             Interlocked.Decrement(ref _readerCount); | ||||
|                 Interlocked.Decrement(ref @this._readerCount); | ||||
|  | ||||
|             } | ||||
|             return @this._cancellationTokenSource.Token; | ||||
|         } | ||||
|         return _cancellationTokenSource.Token; | ||||
|     } | ||||
|  | ||||
|     public bool WriteWaited => _writerCount > 0; | ||||
| @@ -51,21 +57,25 @@ public class AsyncReadWriteLock : IAsyncDisposable | ||||
|     /// <summary> | ||||
|     /// 获取写锁,阻止所有读取。 | ||||
|     /// </summary> | ||||
|     public async ValueTask<IDisposable> WriterLockAsync(CancellationToken cancellationToken) | ||||
|     public ValueTask<IDisposable> WriterLockAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         return WriterLockAsync(this); | ||||
|  | ||||
|         if (Interlocked.Increment(ref _writerCount) == 1) | ||||
|         static async PooledValueTask<IDisposable> WriterLockAsync(AsyncReadWriteLock @this) | ||||
|         { | ||||
|             if (_writePriority) | ||||
|             if (Interlocked.Increment(ref @this._writerCount) == 1) | ||||
|             { | ||||
|                 var cancellationTokenSource = _cancellationTokenSource; | ||||
|                 _cancellationTokenSource = new(); | ||||
|                 await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取 | ||||
|                 cancellationTokenSource.SafeDispose(); | ||||
|                 if (@this._writePriority) | ||||
|                 { | ||||
|                     var cancellationTokenSource = @this._cancellationTokenSource; | ||||
|                     @this._cancellationTokenSource = new(); | ||||
|                     await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取 | ||||
|                     cancellationTokenSource.SafeDispose(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return new Writer(this); | ||||
|             return new Writer(@this); | ||||
|         } | ||||
|     } | ||||
|     private object lockObject = new(); | ||||
|     private void ReleaseWriter() | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| using ThingsGateway.NewLife; | ||||
| using PooledAwait; | ||||
|  | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Threading; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
| @@ -57,57 +59,63 @@ public class CronScheduledTask : DisposeBase, IScheduledTask | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|     private static volatile int NextId = 0; | ||||
|     public void Start() | ||||
|     { | ||||
|         _timer?.Dispose(); | ||||
|         if (Check()) return; | ||||
|         if (_taskAction != null) | ||||
|             _timer = new TimerX(TimerCallback, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false }; | ||||
|             _timer = new TimerX(TimerCallback, _state, _interval, $"{nameof(CronScheduledTask)}{(Interlocked.Increment(ref NextId) / 100)}") { Async = true, Reentrant = false }; | ||||
|         else if (_taskFunc != null || _valueTaskFunc != null) | ||||
|             _timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false }; | ||||
|             _timer = new TimerX(TimerCallbackAsync, _state, _interval, $"{nameof(CronScheduledTask)}{(Interlocked.Increment(ref NextId) / 100)}") { Async = true, Reentrant = false }; | ||||
|     } | ||||
|  | ||||
|     private async ValueTask TimerCallbackAsync(object? state) | ||||
|     private ValueTask TimerCallbackAsync(object? state) | ||||
|     { | ||||
|         if (Check()) return; | ||||
|         if (_taskFunc == null && _valueTaskFunc == null) | ||||
|         return TimerCallbackAsync(this, state); | ||||
|         static async PooledValueTask TimerCallbackAsync(CronScheduledTask @this, object? state) | ||||
|         { | ||||
|             Dispose(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         Interlocked.Increment(ref _pendingTriggers); | ||||
|  | ||||
|         if (Interlocked.Exchange(ref _isRunning, 1) == 1) | ||||
|             return; | ||||
|  | ||||
|         // 减少一个触发次数 | ||||
|         Interlocked.Decrement(ref _pendingTriggers); | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             if (_taskFunc != null) | ||||
|                 await _taskFunc(state, _token).ConfigureAwait(false); | ||||
|             else if (_valueTaskFunc != null) | ||||
|                 await _valueTaskFunc(state, _token).ConfigureAwait(false); | ||||
|         } | ||||
|         catch (OperationCanceledException) | ||||
|         { | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             LogMessage?.LogWarning(ex); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             Interlocked.Exchange(ref _isRunning, 0); | ||||
|         } | ||||
|  | ||||
|         if (Interlocked.Exchange(ref _pendingTriggers, 0) >= 1) | ||||
|         { | ||||
|             if (!Check()) | ||||
|             if (@this.Check()) return; | ||||
|             if (@this._taskFunc == null && @this._valueTaskFunc == null) | ||||
|             { | ||||
|                 SetNext(next); | ||||
|                 @this.Dispose(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             Interlocked.Increment(ref @this._pendingTriggers); | ||||
|  | ||||
|             if (Interlocked.Exchange(ref @this._isRunning, 1) == 1) | ||||
|                 return; | ||||
|  | ||||
|             // 减少一个触发次数 | ||||
|             Interlocked.Decrement(ref @this._pendingTriggers); | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 if (@this._taskFunc != null) | ||||
|                     await @this._taskFunc(state, @this._token).ConfigureAwait(false); | ||||
|                 else if (@this._valueTaskFunc != null) | ||||
|                     await @this._valueTaskFunc(state, @this._token).ConfigureAwait(false); | ||||
|             } | ||||
|             catch (OperationCanceledException) | ||||
|             { | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 @this.LogMessage?.LogWarning(ex); | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 Interlocked.Exchange(ref @this._isRunning, 0); | ||||
|             } | ||||
|  | ||||
|             if (Interlocked.Exchange(ref @this._pendingTriggers, 0) >= 1) | ||||
|             { | ||||
|                 if (!@this.Check()) | ||||
|                 { | ||||
|                     int nextValue = @this.next; | ||||
|                     @this.SetNext(nextValue); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -149,7 +157,8 @@ public class CronScheduledTask : DisposeBase, IScheduledTask | ||||
|         { | ||||
|             if (!Check()) | ||||
|             { | ||||
|                 SetNext(next); | ||||
|                 int nextValue = next; | ||||
|                 SetNext(nextValue); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,146 +0,0 @@ | ||||
| ////------------------------------------------------------------------------------ | ||||
| ////  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| ////  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| ////  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| ////  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| ////  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| ////  使用文档:https://thingsgateway.cn/ | ||||
| ////  QQ群:605534569 | ||||
| ////------------------------------------------------------------------------------ | ||||
|  | ||||
| //using Microsoft.Extensions.Logging; | ||||
|  | ||||
| //using ThingsGateway.NewLife; | ||||
|  | ||||
| //using TouchSocket.Core; | ||||
|  | ||||
| //namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| //[ThingsGateway.DependencyInjection.SuppressSniffer] | ||||
| //public class DoTask | ||||
| //{ | ||||
| //    /// <summary> | ||||
| //    /// 取消令牌 | ||||
| //    /// </summary> | ||||
| //    private CancellationTokenSource? _cancelTokenSource; | ||||
| //    private object? _state; | ||||
|  | ||||
| //    public DoTask(Func<object?, CancellationToken, Task> doWork, ILog logger, object? state = null, string taskName = null) | ||||
| //    { | ||||
| //        DoWork = doWork; Logger = logger; TaskName = taskName; _state = state; | ||||
| //    } | ||||
|  | ||||
| //    /// <summary> | ||||
| //    /// 执行任务方法 | ||||
| //    /// </summary> | ||||
| //    public Func<object?, CancellationToken, Task> DoWork { get; } | ||||
| //    private ILog Logger { get; } | ||||
| //    private Task PrivateTask { get; set; } | ||||
| //    private string TaskName { get; } | ||||
|  | ||||
| //    /// <summary> | ||||
| //    /// 开始 | ||||
| //    /// </summary> | ||||
| //    /// <param name="cancellationToken">调度取消令牌</param> | ||||
| //    public void Start(CancellationToken cancellationToken) | ||||
| //    { | ||||
| //        try | ||||
| //        { | ||||
| //            WaitLock.Wait(cancellationToken); | ||||
|  | ||||
| //            if (cancellationToken.CanBeCanceled) | ||||
| //            { | ||||
| //                _cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); | ||||
| //            } | ||||
| //            else | ||||
| //            { | ||||
| //                _cancelTokenSource = new CancellationTokenSource(); | ||||
| //            } | ||||
|  | ||||
| //            // 异步执行 | ||||
| //            PrivateTask = Do(); | ||||
| //        } | ||||
| //        finally | ||||
| //        { | ||||
| //            WaitLock.Release(); | ||||
| //        } | ||||
| //    } | ||||
|  | ||||
| //    private async Task Do() | ||||
| //    { | ||||
| //        await Task.Yield(); | ||||
| //        while (!_cancelTokenSource.IsCancellationRequested) | ||||
| //        { | ||||
| //            try | ||||
| //            { | ||||
| //                if (_cancelTokenSource.IsCancellationRequested) | ||||
| //                    return; | ||||
| //                await DoWork(_state, _cancelTokenSource.Token).ConfigureAwait(false); | ||||
| //            } | ||||
| //            catch (OperationCanceledException) | ||||
| //            { | ||||
| //            } | ||||
| //            catch (ObjectDisposedException) | ||||
| //            { | ||||
| //            } | ||||
| //            catch (Exception ex) | ||||
| //            { | ||||
| //                Logger?.LogWarning(ex, "DoWork"); | ||||
| //            } | ||||
| //        } | ||||
| //    } | ||||
|  | ||||
| //    private WaitLock WaitLock = new(); | ||||
| //    /// <summary> | ||||
| //    /// 停止操作 | ||||
| //    /// </summary> | ||||
| //    public async Task StopAsync(TimeSpan? waitTime = null) | ||||
| //    { | ||||
| //        try | ||||
| //        { | ||||
| //            await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
| //            try | ||||
| //            { | ||||
| //                _cancelTokenSource?.Cancel(); | ||||
| //                _cancelTokenSource?.Dispose(); | ||||
| //            } | ||||
| //            catch (Exception ex) | ||||
| //            { | ||||
| //                Logger?.LogWarning(ex, "Cancel error"); | ||||
| //            } | ||||
|  | ||||
| //            if (PrivateTask != null) | ||||
| //            { | ||||
| //                try | ||||
| //                { | ||||
| //                    if (TaskName != null) | ||||
| //                        Logger?.LogInformation($"{TaskName} Stoping"); | ||||
| //                    if (waitTime != null) | ||||
| //                        await PrivateTask.WaitAsync(waitTime.Value).ConfigureAwait(false); | ||||
| //                    if (TaskName != null) | ||||
| //                        Logger?.LogInformation($"{TaskName} Stoped"); | ||||
| //                } | ||||
| //                catch (ObjectDisposedException) | ||||
| //                { | ||||
| //                } | ||||
| //                catch (TimeoutException) | ||||
| //                { | ||||
| //                    if (TaskName != null) | ||||
| //                        Logger?.LogWarning($"{TaskName} Stop timeout, exiting wait block"); | ||||
| //                } | ||||
| //                catch (Exception ex) | ||||
| //                { | ||||
| //                    if (TaskName != null) | ||||
| //                        Logger?.LogWarning(ex, $"{TaskName} Stop error"); | ||||
| //                } | ||||
| //                PrivateTask = null; | ||||
|  | ||||
| //            } | ||||
| //        } | ||||
| //        finally | ||||
| //        { | ||||
| //            WaitLock.Release(); | ||||
| //        } | ||||
| //    } | ||||
| //} | ||||
| @@ -1,4 +1,6 @@ | ||||
| using ThingsGateway.NewLife; | ||||
| using PooledAwait; | ||||
|  | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Threading; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
| @@ -44,56 +46,63 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     private static volatile int NextId = 0; | ||||
|     public void Start() | ||||
|     { | ||||
|         _timer?.Dispose(); | ||||
|         if (!Check()) | ||||
|             _timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(ScheduledAsyncTask)) { Async = true, Reentrant = false }; | ||||
|             _timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, $"{nameof(ScheduledAsyncTask)}{(Interlocked.Increment(ref NextId) / 100)}") { Async = true, Reentrant = false }; | ||||
|     } | ||||
|  | ||||
|     private async ValueTask DoAsync(object? state) | ||||
|     private ValueTask DoAsync(object? state) | ||||
|     { | ||||
|         if (Check()) | ||||
|             return; | ||||
|  | ||||
|         if (_taskFunc == null && _valueTaskFunc == null) | ||||
|         return DoAsync(this, state); | ||||
|         static async PooledValueTask DoAsync(ScheduledAsyncTask @this, object? state) | ||||
|         { | ||||
|             Dispose(); | ||||
|             return; | ||||
|         } | ||||
|             if (@this.Check()) | ||||
|                 return; | ||||
|  | ||||
|         Interlocked.Increment(ref _pendingTriggers); | ||||
|  | ||||
|         if (Interlocked.Exchange(ref _isRunning, 1) == 1) | ||||
|             return; | ||||
|  | ||||
|         // 减少一个触发次数 | ||||
|         Interlocked.Decrement(ref _pendingTriggers); | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             if (_taskFunc != null) | ||||
|                 await _taskFunc(state, _token).ConfigureAwait(false); | ||||
|             else if (_valueTaskFunc != null) | ||||
|                 await _valueTaskFunc(state, _token).ConfigureAwait(false); | ||||
|         } | ||||
|         catch (OperationCanceledException) | ||||
|         { | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             LogMessage?.LogWarning(ex); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             Interlocked.Exchange(ref _isRunning, 0); | ||||
|         } | ||||
|  | ||||
|         if (Interlocked.Exchange(ref _pendingTriggers, 0) >= 1) | ||||
|         { | ||||
|             if (!Check() && IntervalMS > 8) | ||||
|             if (@this._taskFunc == null && @this._valueTaskFunc == null) | ||||
|             { | ||||
|                 SetNext(next); | ||||
|                 @this.Dispose(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             Interlocked.Increment(ref @this._pendingTriggers); | ||||
|  | ||||
|             if (Interlocked.Exchange(ref @this._isRunning, 1) == 1) | ||||
|                 return; | ||||
|  | ||||
|             // 减少一个触发次数 | ||||
|             Interlocked.Decrement(ref @this._pendingTriggers); | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 if (@this._taskFunc != null) | ||||
|                     await @this._taskFunc(state, @this._token).ConfigureAwait(false); | ||||
|                 else if (@this._valueTaskFunc != null) | ||||
|                     await @this._valueTaskFunc(state, @this._token).ConfigureAwait(false); | ||||
|             } | ||||
|             catch (OperationCanceledException) | ||||
|             { | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 @this.LogMessage?.LogWarning(ex); | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 Interlocked.Exchange(ref @this._isRunning, 0); | ||||
|             } | ||||
|  | ||||
|             if (Interlocked.Exchange(ref @this._pendingTriggers, 0) >= 1) | ||||
|             { | ||||
|                 if (!@this.Check() && @this.IntervalMS > 8) | ||||
|                 { | ||||
|                     int nextValue = @this.next; | ||||
|                     @this.SetNext(nextValue); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user