Compare commits
	
		
			29 Commits
		
	
	
		
			35edd7dc43
			...
			10.12.11.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					aaa459ebe0 | ||
| 
						 | 
					51a8acbc3e | ||
| 
						 | 
					dc132a1999 | ||
| 
						 | 
					0c31cfcbc5 | ||
| 
						 | 
					0e44bc67cd | ||
| 
						 | 
					12dfbba42c | ||
| 
						 | 
					96b4287f3a | ||
| 
						 | 
					7d406de29f | ||
| 
						 | 
					81f0ef466a | ||
| 
						 | 
					3f2d6b133c | ||
| 
						 | 
					e776dc67eb | ||
| 
						 | 
					bc5827d140 | ||
| 
						 | 
					21838bf4af | ||
| 
						 | 
					6090108597 | ||
| 
						 | 
					b47b9e6f43 | ||
| 
						 | 
					18d1cffb2d | ||
| 
						 | 
					516fd7f235 | ||
| 
						 | 
					2ee16c3533 | ||
| 
						 | 
					7d22f5c78e | ||
| 
						 | 
					3e604ee2fd | ||
| 
						 | 
					47e442874c | ||
| 
						 | 
					2a8c0cbab1 | ||
| 
						 | 
					c26898b49d | ||
| 
						 | 
					00c24d06a3 | ||
| 
						 | 
					3461f34240 | ||
| 
						 | 
					aa1ce08c02 | ||
| 
						 | 
					9c230c2da9 | ||
| 
						 | 
					21215d0379 | ||
| 
						 | 
					7448183791 | 
							
								
								
									
										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,6 +8,9 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
using System.Runtime;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Admin.Application;
 | 
			
		||||
@@ -19,11 +22,7 @@ public class HardwareInfo
 | 
			
		||||
    /// 当前磁盘信息
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public DriveInfo DriveInfo { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 硬件信息获取
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public MachineInfo? MachineInfo { get; set; }
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 主机环境
 | 
			
		||||
@@ -40,19 +39,118 @@ public class HardwareInfo
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public string OsArchitecture { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 唯一编码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public string UUID { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 进程占用内存
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [AutoGenerateColumn(Ignore = true)]
 | 
			
		||||
    public int WorkingSet { get; set; }
 | 
			
		||||
    /// <summary>系统名称</summary>
 | 
			
		||||
    public String OSName { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>系统版本</summary>
 | 
			
		||||
    public String OSVersion { get; set; }
 | 
			
		||||
    public String UUID { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>内存总量。单位MB</summary>
 | 
			
		||||
    public UInt64 Memory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>可用内存。单位MB</summary>
 | 
			
		||||
    public UInt64 AvailableMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>CPU占用率</summary>
 | 
			
		||||
    public Double CpuRate { get; set; }
 | 
			
		||||
    public Double Battery { get; set; }
 | 
			
		||||
    public Double Temperature { get; set; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>处理器型号</summary>
 | 
			
		||||
    public String? Processor { get; set; }
 | 
			
		||||
    #region GC与进程内存信息
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 认为“内存吃紧”的阈值。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC高内存阈值")]
 | 
			
		||||
    public UInt64 HighMemoryLoadThreshold { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 可用内存上限。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC可用内存上限")]
 | 
			
		||||
    public UInt64 TotalAvailableMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>当前托管堆容量。单位:MB</summary>
 | 
			
		||||
    [DisplayName("托管堆容量")]
 | 
			
		||||
    public UInt64 HeapSize { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>托管堆已用内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("托管堆已用")]
 | 
			
		||||
    public UInt64 TotalMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>托管堆碎片大小。单位:MB</summary>
 | 
			
		||||
    [DisplayName("托管堆碎片")]
 | 
			
		||||
    public UInt64 FragmentedBytes { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC识别可用内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC识别可用内存")]
 | 
			
		||||
    public UInt64 GCAvailableMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 已提交的内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC已提交内存")]
 | 
			
		||||
    public UInt64 CommittedBytes { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 累计分配的托管内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC累计分配")]
 | 
			
		||||
    public UInt64 TotalAllocatedBytes { get; set; }
 | 
			
		||||
    /// <summary>GC 暂停累计时间。单位:毫秒</summary>
 | 
			
		||||
    [DisplayName("GC累计暂停时间")]
 | 
			
		||||
    public UInt64 TotalPauseDurationMs { get; set; }
 | 
			
		||||
    /// <summary>GC 代0收集次数</summary>
 | 
			
		||||
    [DisplayName("GC Gen0 次数")]
 | 
			
		||||
    public Int32 GcGen0Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 代1收集次数</summary>
 | 
			
		||||
    [DisplayName("GC Gen1 次数")]
 | 
			
		||||
    public Int32 GcGen1Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 代2收集次数</summary>
 | 
			
		||||
    [DisplayName("GC Gen2 次数")]
 | 
			
		||||
    public Int32 GcGen2Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>Server GC 是否启用</summary>
 | 
			
		||||
    [DisplayName("是否使用Server GC")]
 | 
			
		||||
    public Boolean IsServerGC { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 延迟模式</summary>
 | 
			
		||||
    [DisplayName("GC延迟模式")]
 | 
			
		||||
    public GCLatencyMode? GCLatencyMode { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 固定对象数</summary>
 | 
			
		||||
    [DisplayName("固定对象数")]
 | 
			
		||||
    public Int64 PinnedObjectsCount { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>终结队列挂起对象数</summary>
 | 
			
		||||
    [DisplayName("终结挂起数")]
 | 
			
		||||
    public Int64 FinalizationPendingCount { get; set; }
 | 
			
		||||
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 进程内存信息
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程虚拟内存使用量。单位:MB</summary>
 | 
			
		||||
    [DisplayName("虚拟内存")]
 | 
			
		||||
    public UInt64 VirtualMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程私有内存使用量。单位:MB</summary>
 | 
			
		||||
    [DisplayName("私有内存")]
 | 
			
		||||
    public UInt64 PrivateMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程峰值工作集。单位:MB</summary>
 | 
			
		||||
    [DisplayName("峰值工作集")]
 | 
			
		||||
    public UInt64 PeakWorkingSet { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程当前工作集。单位:MB</summary>
 | 
			
		||||
    [DisplayName("当前工作集")]
 | 
			
		||||
    public UInt64 WorkingSet { get; set; }
 | 
			
		||||
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 更新时间
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public string UpdateTime { get; set; }
 | 
			
		||||
    public DateTime UpdateTime { get; set; }
 | 
			
		||||
    public ulong AppRunTotalMinute { get;  set; }
 | 
			
		||||
    public ulong SystemRunTotalMinute { get;  set; }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,10 @@
 | 
			
		||||
// QQ群:605534569
 | 
			
		||||
// ------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using Microsoft.Extensions.Hosting;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Extension;
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
using ThingsGateway.NewLife.Caching;
 | 
			
		||||
using ThingsGateway.NewLife.Threading;
 | 
			
		||||
@@ -43,7 +41,7 @@ public class HardwareJob : IJob, IHardwareJob
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 运行信息获取
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public HardwareInfo HardwareInfo { get; } = new();
 | 
			
		||||
    public HardwareInfo HardwareInfo { get; private set; }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public HardwareInfoOptions HardwareInfoOptions { get; private set; }
 | 
			
		||||
@@ -76,9 +74,10 @@ public class HardwareJob : IJob, IHardwareJob
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (HardwareInfo.MachineInfo == null)
 | 
			
		||||
                    var machine = MachineInfo.GetCurrent();
 | 
			
		||||
                if (HardwareInfo == null)
 | 
			
		||||
                {
 | 
			
		||||
                    HardwareInfo.MachineInfo = MachineInfo.GetCurrent();
 | 
			
		||||
                    HardwareInfo=machine.AdaptHardwareInfo();
 | 
			
		||||
 | 
			
		||||
                    string currentPath = Directory.GetCurrentDirectory();
 | 
			
		||||
                    DriveInfo drive = new(Path.GetPathRoot(currentPath));
 | 
			
		||||
@@ -88,10 +87,9 @@ public class HardwareJob : IJob, IHardwareJob
 | 
			
		||||
                    HardwareInfo.DriveInfo = drive;
 | 
			
		||||
                    HardwareInfo.OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(); // 系统架构
 | 
			
		||||
                    HardwareInfo.FrameworkDescription = RuntimeInformation.FrameworkDescription; // NET框架
 | 
			
		||||
                    HardwareInfo.Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production";
 | 
			
		||||
                    HardwareInfo.UUID = HardwareInfo.MachineInfo.UUID;
 | 
			
		||||
                    HardwareInfo.Environment = App.HostEnvironment.EnvironmentName;
 | 
			
		||||
 | 
			
		||||
                    HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
 | 
			
		||||
                    HardwareInfo.UpdateTime = TimerX.Now;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
@@ -99,9 +97,12 @@ public class HardwareJob : IJob, IHardwareJob
 | 
			
		||||
            }
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                HardwareInfo.MachineInfo.Refresh();
 | 
			
		||||
                HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat();
 | 
			
		||||
                HardwareInfo.WorkingSet = (Environment.WorkingSet / 1024.0 / 1024.0).ToInt();
 | 
			
		||||
                var machine = MachineInfo.GetCurrent();
 | 
			
		||||
                machine.Refresh();
 | 
			
		||||
                machine.AdaptHardwareInfo(HardwareInfo);
 | 
			
		||||
                HardwareInfo.AppRunTotalMinute = (ulong)Runtime.AppTickCount64 / 1000 /60;
 | 
			
		||||
                HardwareInfo.SystemRunTotalMinute = (ulong)Runtime.TickCount64 / 1000 /60;
 | 
			
		||||
                HardwareInfo.UpdateTime = TimerX.Now;
 | 
			
		||||
                error = false;
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
@@ -123,10 +124,10 @@ public class HardwareJob : IJob, IHardwareJob
 | 
			
		||||
                            {
 | 
			
		||||
                                Date = TimerX.Now,
 | 
			
		||||
                                DriveUsage = (100 - (HardwareInfo.DriveInfo.TotalFreeSpace * 100.00 / HardwareInfo.DriveInfo.TotalSize)).ToInt(),
 | 
			
		||||
                                Battery = (HardwareInfo.MachineInfo.Battery * 100).ToInt(),
 | 
			
		||||
                                Battery = (HardwareInfo.Battery * 100).ToInt(),
 | 
			
		||||
                                MemoryUsage = (HardwareInfo.WorkingSet),
 | 
			
		||||
                                CpuUsage = (HardwareInfo.MachineInfo.CpuRate * 100).ToInt(),
 | 
			
		||||
                                Temperature = (HardwareInfo.MachineInfo.Temperature).ToInt(),
 | 
			
		||||
                                CpuUsage = (HardwareInfo.CpuRate * 100).ToInt(),
 | 
			
		||||
                                Temperature = (HardwareInfo.Temperature).ToInt(),
 | 
			
		||||
                            };
 | 
			
		||||
                            await _db.InsertableT(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
 | 
			
		||||
                            MemoryCache.Remove(CacheKey);
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ public class HistoryHardwareInfo
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    [SugarColumn(ColumnDescription = "内存")]
 | 
			
		||||
    public int MemoryUsage { get; set; }
 | 
			
		||||
    public UInt64 MemoryUsage { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    [SugarColumn(ColumnDescription = "CPU使用率")]
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
    "UserNoModule": "This account has not been assigned a module. Please contact the administrator",
 | 
			
		||||
    "UserNull": "User {0} does not exist"
 | 
			
		||||
  },
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
 | 
			
		||||
    "UserExpire": "User expired, please login again"
 | 
			
		||||
  },
 | 
			
		||||
@@ -46,12 +46,44 @@
 | 
			
		||||
    "FileTypeError": "Not supported format {0}"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Application.HardwareInfo": {
 | 
			
		||||
    "Environment": "HostEnvironment",
 | 
			
		||||
    "FrameworkDescription": ".NETFramework",
 | 
			
		||||
    "OsArchitecture": "System Architecture",
 | 
			
		||||
    "UpdateTime": "UpdateTime",
 | 
			
		||||
    "UUID": "UUID"
 | 
			
		||||
    "DriveInfo": "Current Disk Info",
 | 
			
		||||
    "AppRunTotalMinute": "AppRunTotalMinute(min)",
 | 
			
		||||
    "SystemRunTotalMinute": "SystemRunTotalMinute(min)",
 | 
			
		||||
    "Environment": "Host Environment",
 | 
			
		||||
    "FrameworkDescription": ".NET Framework",
 | 
			
		||||
    "OsArchitecture": "OS Architecture",
 | 
			
		||||
    "OSName": "OS Name",
 | 
			
		||||
    "OSVersion": "OS Version",
 | 
			
		||||
    "UUID": "UUID",
 | 
			
		||||
    "Memory": "Total Memory",
 | 
			
		||||
    "AvailableMemory": "Available Memory",
 | 
			
		||||
    "CpuRate": "CPU Usage",
 | 
			
		||||
    "Battery": "Battery Level",
 | 
			
		||||
    "Temperature": "Temperature",
 | 
			
		||||
    "Processor": "Processor Model",
 | 
			
		||||
    "HighMemoryLoadThreshold": "GC High Memory Threshold",
 | 
			
		||||
    "TotalAvailableMemory": "GC Total Available Memory",
 | 
			
		||||
    "HeapSize": "Managed Heap Size",
 | 
			
		||||
    "TotalMemory": "Managed Heap Used",
 | 
			
		||||
    "FragmentedBytes": "Managed Heap Fragmented",
 | 
			
		||||
    "GCAvailableMemory": "GC Available Memory",
 | 
			
		||||
    "CommittedBytes": "GC Committed Bytes",
 | 
			
		||||
    "TotalAllocatedBytes": "GC Total Allocated (MB)",
 | 
			
		||||
    "TotalPauseDurationMs": "GC Total Pause Duration (ms)",
 | 
			
		||||
    "GcGen0Count": "GC Gen0 Count",
 | 
			
		||||
    "GcGen1Count": "GC Gen1 Count",
 | 
			
		||||
    "GcGen2Count": "GC Gen2 Count",
 | 
			
		||||
    "IsServerGC": "Server GC",
 | 
			
		||||
    "GCLatencyMode": "GC Latency Mode",
 | 
			
		||||
    "PinnedObjectsCount": "Pinned Objects Count",
 | 
			
		||||
    "FinalizationPendingCount": "Finalization Pending Count",
 | 
			
		||||
    "VirtualMemory": "Virtual Memory",
 | 
			
		||||
    "PrivateMemory": "Private Memory",
 | 
			
		||||
    "PeakWorkingSet": "Peak Working Set",
 | 
			
		||||
    "WorkingSet": "Current Working Set",
 | 
			
		||||
    "UpdateTime": "Update Time"
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  "ThingsGateway.Admin.Application.HardwareJob": {
 | 
			
		||||
    "GetHardwareInfoFail": "Get Hardwareinfo Fail"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -46,11 +46,42 @@
 | 
			
		||||
    "FileTypeError": "不支持 {0} 格式"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Application.HardwareInfo": {
 | 
			
		||||
    "DriveInfo": "当前磁盘信息",
 | 
			
		||||
    "AppRunTotalMinute": "软件运行时长(min)",
 | 
			
		||||
    "SystemRunTotalMinute": "系统运行时长(min)",
 | 
			
		||||
    "Environment": "主机环境",
 | 
			
		||||
    "FrameworkDescription": "NET框架",
 | 
			
		||||
    "FrameworkDescription": ".NET 框架",
 | 
			
		||||
    "OsArchitecture": "系统架构",
 | 
			
		||||
    "UpdateTime": "更新时间",
 | 
			
		||||
    "UUID": "唯一编码"
 | 
			
		||||
    "OSName": "系统名称",
 | 
			
		||||
    "OSVersion": "系统版本",
 | 
			
		||||
    "UUID": "UUID",
 | 
			
		||||
    "Memory": "系统总内存",
 | 
			
		||||
    "AvailableMemory": "系统可用内存",
 | 
			
		||||
    "CpuRate": "CPU 占用率",
 | 
			
		||||
    "Battery": "电池电量",
 | 
			
		||||
    "Temperature": "温度",
 | 
			
		||||
    "Processor": "处理器型号",
 | 
			
		||||
    "HighMemoryLoadThreshold": "GC 高内存阈值",
 | 
			
		||||
    "TotalAvailableMemory": "GC 总内存阈值",
 | 
			
		||||
    "HeapSize": "托管堆容量",
 | 
			
		||||
    "TotalMemory": "托管对象占用",
 | 
			
		||||
    "FragmentedBytes": "托管堆碎片",
 | 
			
		||||
    "GCAvailableMemory": "GC 可用内存",
 | 
			
		||||
    "CommittedBytes": "GC 提交内存总量",
 | 
			
		||||
    "TotalAllocatedBytes": "GC 累计分配(MB)",
 | 
			
		||||
    "TotalPauseDurationMs": "GC 累计暂停时间(ms)",
 | 
			
		||||
    "GcGen0Count": "GC Gen0 次数",
 | 
			
		||||
    "GcGen1Count": "GC Gen1 次数",
 | 
			
		||||
    "GcGen2Count": "GC Gen2 次数",
 | 
			
		||||
    "IsServerGC": "Server GC",
 | 
			
		||||
    "GCLatencyMode": "GC 延迟模式",
 | 
			
		||||
    "PinnedObjectsCount": "固定对象数",
 | 
			
		||||
    "FinalizationPendingCount": "终结挂起数",
 | 
			
		||||
    "VirtualMemory": "虚拟内存",
 | 
			
		||||
    "PrivateMemory": "私有内存",
 | 
			
		||||
    "PeakWorkingSet": "峰值工作集",
 | 
			
		||||
    "WorkingSet": "当前工作集",
 | 
			
		||||
    "UpdateTime": "更新时间"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Admin.Application.HardwareJob": {
 | 
			
		||||
    "GetHardwareInfoFail": "获取硬件信息出错"
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,20 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using BootstrapBlazor.Components;
 | 
			
		||||
 | 
			
		||||
using Riok.Mapperly.Abstractions;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Admin.Application;
 | 
			
		||||
 | 
			
		||||
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
 | 
			
		||||
public static partial class AdminMapper
 | 
			
		||||
{
 | 
			
		||||
    public static partial HardwareInfo AdaptHardwareInfo(this MachineInfo src);
 | 
			
		||||
        public static partial void AdaptHardwareInfo(this MachineInfo src, HardwareInfo dto);
 | 
			
		||||
 | 
			
		||||
    public static partial LoginInput AdaptLoginInput(this OpenApiLoginInput src);
 | 
			
		||||
    public static partial OpenApiLoginOutput AdaptOpenApiLoginOutput(this LoginOutput src);
 | 
			
		||||
    public static partial SessionOutput AdaptSessionOutput(this SysUser src);
 | 
			
		||||
 
 | 
			
		||||
@@ -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" />
 | 
			
		||||
 
 | 
			
		||||
@@ -92,7 +92,8 @@ public class Startup : AppStartup
 | 
			
		||||
             options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
             options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
 | 
			
		||||
             options.MaxBufferedUnacknowledgedRenderBatches = 20;
 | 
			
		||||
             options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
 | 
			
		||||
             options.DisconnectedCircuitMaxRetained = 1;
 | 
			
		||||
             options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
			
		||||
         })
 | 
			
		||||
         .AddHubOptions(options =>
 | 
			
		||||
         {
 | 
			
		||||
@@ -103,6 +104,7 @@ public class Startup : AppStartup
 | 
			
		||||
             options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
 | 
			
		||||
             options.KeepAliveInterval = TimeSpan.FromSeconds(15);
 | 
			
		||||
             options.HandshakeTimeout = TimeSpan.FromSeconds(30);
 | 
			
		||||
 | 
			
		||||
         });
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
@@ -112,7 +114,8 @@ public class Startup : AppStartup
 | 
			
		||||
            options.RootComponents.MaxJSRootComponents = 500;
 | 
			
		||||
            options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
 | 
			
		||||
            options.MaxBufferedUnacknowledgedRenderBatches = 20;
 | 
			
		||||
            options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
 | 
			
		||||
             options.DisconnectedCircuitMaxRetained = 1;
 | 
			
		||||
             options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
 | 
			
		||||
        }).AddHubOptions(options =>
 | 
			
		||||
        {
 | 
			
		||||
            //单个传入集线器消息的最大大小。默认 32 KB
 | 
			
		||||
 
 | 
			
		||||
@@ -15,15 +15,11 @@
 | 
			
		||||
		<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
 | 
			
		||||
		<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon>
 | 
			
		||||
 | 
			
		||||
		<CETCompat>false</CETCompat>
 | 
			
		||||
		<ServerGarbageCollection>true</ServerGarbageCollection>
 | 
			
		||||
		<!--动态适用GC-->
 | 
			
		||||
		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
 | 
			
		||||
		<CETCompat>false</CETCompat>
 | 
			
		||||
		<!--使用自托管线程池-->
 | 
			
		||||
		<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
 | 
			
		||||
 | 
			
		||||
		<!--使用工作站GC-->
 | 
			
		||||
		<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
 | 
			
		||||
 | 
			
		||||
		
 | 
			
		||||
		<!--<PlatformTarget>x86</PlatformTarget>-->
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,9 @@ using System.ComponentModel.DataAnnotations;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Common.Extension;
 | 
			
		||||
 | 
			
		||||
using Swashbuckle.AspNetCore.SwaggerGen;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#if NET8_0_OR_GREATER
 | 
			
		||||
using System.Collections.Frozen;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -27,7 +30,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 +43,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 +107,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 +207,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 +215,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 +224,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 +240,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 +258,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 +277,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 +287,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 +316,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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										241
									
								
								src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,241 @@
 | 
			
		||||
using ThingsGateway.NewLife.Log;
 | 
			
		||||
using ThingsGateway.NewLife.Reflection;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.NewLife.Collections;
 | 
			
		||||
 | 
			
		||||
/// <summary>资源池。支持空闲释放,主要用于数据库连接池和网络连接池</summary>
 | 
			
		||||
/// <remarks>
 | 
			
		||||
/// 文档 https://newlifex.com/core/object_pool
 | 
			
		||||
/// </remarks>
 | 
			
		||||
/// <typeparam name="T"></typeparam>
 | 
			
		||||
public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
 | 
			
		||||
{
 | 
			
		||||
    #region 属性
 | 
			
		||||
    /// <summary>名称</summary>
 | 
			
		||||
    public String Name { get; set; }
 | 
			
		||||
 | 
			
		||||
    private Int32 _FreeCount;
 | 
			
		||||
    /// <summary>空闲个数</summary>
 | 
			
		||||
    public Int32 FreeCount => _FreeCount;
 | 
			
		||||
 | 
			
		||||
    private Int32 _BusyCount;
 | 
			
		||||
    /// <summary>繁忙个数</summary>
 | 
			
		||||
    public Int32 BusyCount => _BusyCount;
 | 
			
		||||
 | 
			
		||||
    /// <summary>最大个数。默认0,0表示无上限</summary>
 | 
			
		||||
    public Int32 Max { get; set; } = 0;
 | 
			
		||||
 | 
			
		||||
    private readonly object _syncRoot = new();
 | 
			
		||||
 | 
			
		||||
    /// <summary>基础空闲集合。只保存最小个数,最热部分</summary>
 | 
			
		||||
    private readonly Stack<T> _free = new();
 | 
			
		||||
 | 
			
		||||
    /// <summary>借出去的放在这</summary>
 | 
			
		||||
    private readonly HashSet<T> _busy = new();
 | 
			
		||||
 | 
			
		||||
    //private readonly Object SyncRoot = new();
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 构造
 | 
			
		||||
    /// <summary>实例化一个资源池</summary>
 | 
			
		||||
    public ObjectPoolLock()
 | 
			
		||||
    {
 | 
			
		||||
        var str = GetType().Name;
 | 
			
		||||
        if (str.Contains('`')) str = str.Substring(null, "`");
 | 
			
		||||
        if (str != "Pool")
 | 
			
		||||
            Name = str;
 | 
			
		||||
        else
 | 
			
		||||
            Name = $"Pool<{typeof(T).Name}>";
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    ~ObjectPoolLock()
 | 
			
		||||
    {
 | 
			
		||||
        this.TryDispose();
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>销毁</summary>
 | 
			
		||||
    /// <param name="disposing"></param>
 | 
			
		||||
    protected override void Dispose(Boolean disposing)
 | 
			
		||||
    {
 | 
			
		||||
        base.Dispose(disposing);
 | 
			
		||||
 | 
			
		||||
        WriteLog($"Dispose {typeof(T).FullName} FreeCount={FreeCount:n0} BusyCount={BusyCount:n0}");
 | 
			
		||||
 | 
			
		||||
        Clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private volatile Boolean _inited;
 | 
			
		||||
    private void Init()
 | 
			
		||||
    {
 | 
			
		||||
        if (_inited) return;
 | 
			
		||||
 | 
			
		||||
        lock (lockThis)
 | 
			
		||||
        {
 | 
			
		||||
            if (_inited) return;
 | 
			
		||||
            _inited = true;
 | 
			
		||||
 | 
			
		||||
            WriteLog($"Init {typeof(T).FullName} Max={Max}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 主方法
 | 
			
		||||
    /// <summary>借出</summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public virtual T Get()
 | 
			
		||||
    {
 | 
			
		||||
        T? pi = null;
 | 
			
		||||
        do
 | 
			
		||||
        {
 | 
			
		||||
            lock (_syncRoot)
 | 
			
		||||
            {
 | 
			
		||||
                if (_free.Count > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    pi = _free.Pop();
 | 
			
		||||
                    _FreeCount--;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    if (Max > 0 && BusyCount >= Max)
 | 
			
		||||
                    {
 | 
			
		||||
                        var msg = $"申请失败,已有 {BusyCount:n0} 达到或超过最大值 {Max:n0}";
 | 
			
		||||
                        WriteLog("Acquire Max " + msg);
 | 
			
		||||
                        throw new Exception(Name + " " + msg);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    pi = OnCreate();
 | 
			
		||||
                    if (BusyCount == 0) Init();
 | 
			
		||||
 | 
			
		||||
#if DEBUG
 | 
			
		||||
                    WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, BusyCount + 1);
 | 
			
		||||
#endif
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 如果拿到的对象不可用,则重新借
 | 
			
		||||
        } while (pi == null || !OnGet(pi));
 | 
			
		||||
 | 
			
		||||
        lock (_syncRoot)
 | 
			
		||||
        {
 | 
			
		||||
            // 加入繁忙集合
 | 
			
		||||
            _busy.Add(pi);
 | 
			
		||||
 | 
			
		||||
            _BusyCount++;
 | 
			
		||||
        }
 | 
			
		||||
        return pi;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>借出时是否可用</summary>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    protected virtual Boolean OnGet(T value) => true;
 | 
			
		||||
 | 
			
		||||
    /// <summary>申请资源包装项,Dispose时自动归还到池中</summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public PoolItem<T> GetItem() => new(this, Get());
 | 
			
		||||
 | 
			
		||||
    /// <summary>归还</summary>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    public virtual Boolean Return(T value)
 | 
			
		||||
    {
 | 
			
		||||
        if (value == null) return false;
 | 
			
		||||
        lock (_syncRoot)
 | 
			
		||||
        {
 | 
			
		||||
            // 从繁忙队列找到并移除缓存项
 | 
			
		||||
            if (!_busy.Remove(value))
 | 
			
		||||
            {
 | 
			
		||||
#if DEBUG
 | 
			
		||||
                WriteLog("Return Error");
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _BusyCount--;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 是否可用
 | 
			
		||||
        if (!OnReturn(value))
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (value is DisposeBase db && db.Disposed)
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        lock (_syncRoot)
 | 
			
		||||
        {
 | 
			
		||||
            _free.Push(value);
 | 
			
		||||
            _FreeCount++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>归还时是否可用</summary>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    protected virtual Boolean OnReturn(T value) => true;
 | 
			
		||||
 | 
			
		||||
    /// <summary>清空已有对象</summary>
 | 
			
		||||
    public virtual Int32 Clear()
 | 
			
		||||
    {
 | 
			
		||||
        var count = _FreeCount + _BusyCount;
 | 
			
		||||
 | 
			
		||||
        //_busy.Clear();
 | 
			
		||||
        //_BusyCount = 0;
 | 
			
		||||
 | 
			
		||||
        //_free.Clear();
 | 
			
		||||
        //while (_free2.TryDequeue(out var rs)) ;
 | 
			
		||||
        //_FreeCount = 0;
 | 
			
		||||
 | 
			
		||||
        lock (_syncRoot)
 | 
			
		||||
        {
 | 
			
		||||
            count = _FreeCount + _BusyCount;
 | 
			
		||||
 | 
			
		||||
            while (_free.Count > 0)
 | 
			
		||||
            {
 | 
			
		||||
                var pi = _free.Pop();
 | 
			
		||||
                OnDispose(pi);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _FreeCount = 0;
 | 
			
		||||
 | 
			
		||||
            foreach (var item in _busy)
 | 
			
		||||
            {
 | 
			
		||||
                OnDispose(item);
 | 
			
		||||
            }
 | 
			
		||||
            _busy.Clear();
 | 
			
		||||
            _BusyCount = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>销毁</summary>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    protected virtual void OnDispose(T? value) => value.TryDispose();
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 重载
 | 
			
		||||
    /// <summary>创建实例</summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    protected virtual T? OnCreate() => (T?)typeof(T).CreateInstance();
 | 
			
		||||
    #endregion
 | 
			
		||||
    protected object lockThis = new();
 | 
			
		||||
 | 
			
		||||
    #region 日志
 | 
			
		||||
    /// <summary>日志</summary>
 | 
			
		||||
    public ILog Log { get; set; } = Logger.Null;
 | 
			
		||||
 | 
			
		||||
    /// <summary>写日志</summary>
 | 
			
		||||
    /// <param name="format"></param>
 | 
			
		||||
    /// <param name="args"></param>
 | 
			
		||||
    public void WriteLog(String format, params Object?[] args)
 | 
			
		||||
    {
 | 
			
		||||
        if (Log?.Enable != true) return;
 | 
			
		||||
 | 
			
		||||
        Log.Info(Name + "." + format, args);
 | 
			
		||||
    }
 | 
			
		||||
    #endregion
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,48 @@
 | 
			
		||||
namespace ThingsGateway.NewLife.Collections;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
 | 
			
		||||
internal struct ValueStopwatch
 | 
			
		||||
{
 | 
			
		||||
#if !NET7_0_OR_GREATER
 | 
			
		||||
    private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    private readonly long _startTimestamp;
 | 
			
		||||
 | 
			
		||||
    public bool IsActive => _startTimestamp != 0;
 | 
			
		||||
 | 
			
		||||
    private ValueStopwatch(long startTimestamp)
 | 
			
		||||
    {
 | 
			
		||||
        _startTimestamp = startTimestamp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp());
 | 
			
		||||
 | 
			
		||||
    public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp)
 | 
			
		||||
    {
 | 
			
		||||
#if !NET7_0_OR_GREATER
 | 
			
		||||
        var timestampDelta = endingTimestamp - startingTimestamp;
 | 
			
		||||
        var ticks = (long)(TimestampToTicks * timestampDelta);
 | 
			
		||||
        return new TimeSpan(ticks);
 | 
			
		||||
#else
 | 
			
		||||
        return Stopwatch.GetElapsedTime(startingTimestamp, endingTimestamp);
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TimeSpan GetElapsedTime()
 | 
			
		||||
    {
 | 
			
		||||
        // Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0.
 | 
			
		||||
        // So it being 0 is a clear indication of default(ValueStopwatch)
 | 
			
		||||
        if (!IsActive)
 | 
			
		||||
        {
 | 
			
		||||
            throw new InvalidOperationException("An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var end = Stopwatch.GetTimestamp();
 | 
			
		||||
 | 
			
		||||
        return GetElapsedTime(_startTimestamp, end);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -7,17 +7,22 @@ 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;
 | 
			
		||||
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Runtime;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#if NETFRAMEWORK
 | 
			
		||||
using System.Management;
 | 
			
		||||
 | 
			
		||||
using Microsoft.VisualBasic.Devices;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
#if NETFRAMEWORK || NET6_0_OR_GREATER
 | 
			
		||||
using Microsoft.Win32;
 | 
			
		||||
@@ -42,7 +47,7 @@ public interface IMachineInfo
 | 
			
		||||
/// 
 | 
			
		||||
/// 刷新信息成本较高,建议采用单例模式
 | 
			
		||||
/// </remarks>
 | 
			
		||||
public class MachineInfo : IExtend
 | 
			
		||||
public class MachineInfo
 | 
			
		||||
{
 | 
			
		||||
    #region 属性
 | 
			
		||||
    /// <summary>系统名称</summary>
 | 
			
		||||
@@ -88,11 +93,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 +121,98 @@ public class MachineInfo : IExtend
 | 
			
		||||
    [DisplayName("电池剩余")]
 | 
			
		||||
    public Double Battery { get; set; }
 | 
			
		||||
 | 
			
		||||
    private readonly Dictionary<String, Object?> _items = [];
 | 
			
		||||
    IDictionary<String, Object?> IExtend.Items => _items;
 | 
			
		||||
#if NET6_0_OR_GREATER
 | 
			
		||||
    #region GC与进程内存信息
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 认为“内存吃紧”的阈值。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC高内存阈值")]
 | 
			
		||||
    public UInt64 HighMemoryLoadThreshold { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 可用内存上限。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC可用内存上限")]
 | 
			
		||||
    public UInt64 TotalAvailableMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>当前托管堆容量。单位:MB</summary>
 | 
			
		||||
    [DisplayName("托管堆容量")]
 | 
			
		||||
    public UInt64 HeapSize { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>托管堆已用内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("托管堆已用")]
 | 
			
		||||
    public UInt64 TotalMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>托管堆碎片大小。单位:MB</summary>
 | 
			
		||||
    [DisplayName("托管堆碎片")]
 | 
			
		||||
    public UInt64 FragmentedBytes { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC识别可用内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC识别可用内存")]
 | 
			
		||||
    public UInt64 GCAvailableMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 已提交的内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC已提交内存")]
 | 
			
		||||
    public UInt64 CommittedBytes { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 累计分配的托管内存。单位:MB</summary>
 | 
			
		||||
    [DisplayName("GC累计分配")]
 | 
			
		||||
    public UInt64 TotalAllocatedBytes { get; set; }
 | 
			
		||||
#if NET8_0_OR_GREATER
 | 
			
		||||
    /// <summary>GC 暂停累计时间。单位:毫秒</summary>
 | 
			
		||||
    [DisplayName("GC累计暂停时间")]
 | 
			
		||||
    public UInt64 TotalPauseDurationMs { get; set; }
 | 
			
		||||
#endif
 | 
			
		||||
    /// <summary>GC 代0收集次数</summary>
 | 
			
		||||
    [DisplayName("GC Gen0 次数")]
 | 
			
		||||
    public Int32 GcGen0Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 代1收集次数</summary>
 | 
			
		||||
    [DisplayName("GC Gen1 次数")]
 | 
			
		||||
    public Int32 GcGen1Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 代2收集次数</summary>
 | 
			
		||||
    [DisplayName("GC Gen2 次数")]
 | 
			
		||||
    public Int32 GcGen2Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>Server GC 是否启用</summary>
 | 
			
		||||
    [DisplayName("是否使用Server GC")]
 | 
			
		||||
    public Boolean IsServerGC { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 延迟模式</summary>
 | 
			
		||||
    [DisplayName("GC延迟模式")]
 | 
			
		||||
    public GCLatencyMode? GCLatencyMode { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>GC 固定对象数</summary>
 | 
			
		||||
    [DisplayName("固定对象数")]
 | 
			
		||||
    public Int64 PinnedObjectsCount { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>终结队列挂起对象数</summary>
 | 
			
		||||
    [DisplayName("终结挂起数")]
 | 
			
		||||
    public Int64 FinalizationPendingCount { get; set; }
 | 
			
		||||
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 进程内存信息
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程虚拟内存使用量。单位:MB</summary>
 | 
			
		||||
    [DisplayName("虚拟内存")]
 | 
			
		||||
    public UInt64 VirtualMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程私有内存使用量。单位:MB</summary>
 | 
			
		||||
    [DisplayName("私有内存")]
 | 
			
		||||
    public UInt64 PrivateMemory { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程峰值工作集。单位:MB</summary>
 | 
			
		||||
    [DisplayName("峰值工作集")]
 | 
			
		||||
    public UInt64 PeakWorkingSet { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>进程当前工作集。单位:MB</summary>
 | 
			
		||||
    [DisplayName("当前工作集")]
 | 
			
		||||
    public UInt64 WorkingSet { get; set; }
 | 
			
		||||
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>获取 或 设置 扩展属性数据</summary>
 | 
			
		||||
    /// <param name="key"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public Object? this[String key] { get => _items.TryGetValue(key, out var obj) ? obj : null; set => _items[key] = value; }
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 全局静态
 | 
			
		||||
@@ -569,10 +659,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 +710,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 +805,87 @@ public class MachineInfo : IExtend
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 🐳 容器内存使用
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    private static (ulong Total, ulong Used) GetCGroupMemoryUsage()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            string[] limitPaths = {
 | 
			
		||||
            "/sys/fs/cgroup/memory/memory.limit_in_bytes", // cgroup v1
 | 
			
		||||
            "/sys/fs/cgroup/memory.max"                    // cgroup v2
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
            string[] usagePaths = {
 | 
			
		||||
            "/sys/fs/cgroup/memory/memory.usage_in_bytes",  // cgroup v1
 | 
			
		||||
            "/sys/fs/cgroup/memory.current"                 // cgroup v2
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
            ulong total = ReadFirstAvailable(limitPaths);
 | 
			
		||||
            ulong used = ReadFirstAvailable(usagePaths);
 | 
			
		||||
 | 
			
		||||
            // 容器无内存限制时 total 通常是 2^63-1,忽略
 | 
			
		||||
            if (total > (1UL << 60)) total = 0;
 | 
			
		||||
 | 
			
		||||
            return (total, used);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception)
 | 
			
		||||
        {
 | 
			
		||||
            return (0, 0);
 | 
			
		||||
        }
 | 
			
		||||
        static ulong ReadFirstAvailable(string[] paths)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var path in paths)
 | 
			
		||||
            {
 | 
			
		||||
                if (File.Exists(path))
 | 
			
		||||
                {
 | 
			
		||||
                    var content = File.ReadAllText(path).Trim();
 | 
			
		||||
                    if (content == "max") return ulong.MaxValue;
 | 
			
		||||
                    if (ulong.TryParse(content, out var value)) return value;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void RefreshLinux()
 | 
			
		||||
    {
 | 
			
		||||
        var dic = ReadInfo("/proc/meminfo");
 | 
			
		||||
        if (dic != null)
 | 
			
		||||
        var (totalMemory, usedMemory) = GetCGroupMemoryUsage();
 | 
			
		||||
        if (totalMemory > 0 && usedMemory > 0 && totalMemory < ulong.MaxValue / 2)
 | 
			
		||||
        {
 | 
			
		||||
            if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty())
 | 
			
		||||
                Memory = (UInt64)str.TrimEnd(" kB").ToLong();
 | 
			
		||||
 | 
			
		||||
            ulong ma = 0;
 | 
			
		||||
            if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty())
 | 
			
		||||
            Memory = totalMemory / 1024 / 1024;
 | 
			
		||||
            AvailableMemory = (totalMemory - usedMemory) / 1024 / 1024;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            var dic = ReadInfo("/proc/meminfo");
 | 
			
		||||
            if (dic != null)
 | 
			
		||||
            {
 | 
			
		||||
                ma = (UInt64)(str.TrimEnd(" kB").ToLong());
 | 
			
		||||
                if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty())
 | 
			
		||||
                    Memory = (UInt64)str.TrimEnd(" kB").ToLong();
 | 
			
		||||
 | 
			
		||||
                ulong ma = 0;
 | 
			
		||||
                if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty())
 | 
			
		||||
                {
 | 
			
		||||
                    ma = (UInt64)(str.TrimEnd(" kB").ToLong() / 1024);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //低于3.14内核的版本用 free+cache
 | 
			
		||||
                var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0);
 | 
			
		||||
                var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0);
 | 
			
		||||
                var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0);
 | 
			
		||||
 | 
			
		||||
                var free = mf + mc + bf;
 | 
			
		||||
 | 
			
		||||
                AvailableMemory = ma > free ? ma : free;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //低于3.14内核的版本用 free+cache
 | 
			
		||||
            var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() ?? 0);
 | 
			
		||||
            var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0);
 | 
			
		||||
            var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0);
 | 
			
		||||
 | 
			
		||||
            var free = mf + mc + bf;
 | 
			
		||||
 | 
			
		||||
            AvailableMemory = ma > free ? ma : free;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // A2/A4温度获取,Buildroot,CPU温度和主板温度
 | 
			
		||||
 
 | 
			
		||||
@@ -10,13 +10,18 @@
 | 
			
		||||
//  感谢您的下载和使用
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation;
 | 
			
		||||
namespace ThingsGateway.NewLife;
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
 | 
			
		||||
public sealed class ReusableCancellationTokenSource : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    ~ReusableCancellationTokenSource()
 | 
			
		||||
    {
 | 
			
		||||
        Dispose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private readonly Timer _timer;
 | 
			
		||||
    private CancellationTokenSource? _cts;
 | 
			
		||||
 | 
			
		||||
@@ -47,7 +52,7 @@ public sealed class ReusableCancellationTokenSource : IDisposable
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取一个 CTS,并启动超时
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public CancellationTokenSource GetTokenSource(long timeout, CancellationToken external1 = default, CancellationToken external2 = default, CancellationToken external3 = default)
 | 
			
		||||
    public CancellationToken GetTokenSource(long timeout, CancellationToken external1 = default, CancellationToken external2 = default, CancellationToken external3 = default)
 | 
			
		||||
    {
 | 
			
		||||
        TimeoutStatus = false;
 | 
			
		||||
 | 
			
		||||
@@ -57,7 +62,7 @@ public sealed class ReusableCancellationTokenSource : IDisposable
 | 
			
		||||
        // 启动 Timer
 | 
			
		||||
        _timer.Change(timeout, Timeout.Infinite);
 | 
			
		||||
 | 
			
		||||
        return _cts;
 | 
			
		||||
        return _cts.Token;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -71,15 +76,16 @@ public sealed class ReusableCancellationTokenSource : IDisposable
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public void Cancel()
 | 
			
		||||
    {
 | 
			
		||||
        _cts?.SafeCancel();
 | 
			
		||||
        try { _cts?.Cancel(); } catch { }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        _cts?.SafeCancel();
 | 
			
		||||
        _cts?.SafeDispose();
 | 
			
		||||
        _linkedCtsCache.SafeDispose();
 | 
			
		||||
        _timer.SafeDispose();
 | 
			
		||||
        try { _cts?.Cancel(); } catch { }
 | 
			
		||||
        try { _cts?.Dispose(); } catch { }
 | 
			
		||||
        try { _linkedCtsCache?.Dispose(); } catch { }
 | 
			
		||||
        try { _timer?.Dispose(); } catch { }
 | 
			
		||||
        GC.SuppressFinalize(this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -180,6 +180,12 @@ public static class Runtime
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 扩展
 | 
			
		||||
 | 
			
		||||
    public static Int64 AppStartTick = TickCount64;
 | 
			
		||||
 | 
			
		||||
    /// <summary>软件启动以来的毫秒数</summary>
 | 
			
		||||
    public static Int64 AppTickCount64 => TickCount64-AppStartTick;
 | 
			
		||||
 | 
			
		||||
#if NETCOREAPP3_1_OR_GREATER
 | 
			
		||||
    /// <summary>系统启动以来的毫秒数</summary>
 | 
			
		||||
    public static Int64 TickCount64 => Environment.TickCount64;
 | 
			
		||||
@@ -196,6 +202,8 @@ public static class Runtime
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>获取当前UTC时间。基于全局时间提供者,在星尘应用中会屏蔽服务器时间差</summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static DateTimeOffset UtcNow => TimerScheduler.GlobalTimeProvider.GetUtcNow();
 | 
			
		||||
@@ -248,7 +256,7 @@ public static class Runtime
 | 
			
		||||
 | 
			
		||||
        return dic;
 | 
			
		||||
    }
 | 
			
		||||
    #endregion
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
    #region 设置
 | 
			
		||||
    private static Boolean? _createConfigOnMissing;
 | 
			
		||||
 
 | 
			
		||||
@@ -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.108</PluginVersion>
 | 
			
		||||
		<ProPluginVersion>10.11.108</ProPluginVersion>
 | 
			
		||||
		<DefaultVersion>10.11.108</DefaultVersion>
 | 
			
		||||
		<AuthenticationVersion>10.11.6</AuthenticationVersion>
 | 
			
		||||
		<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
 | 
			
		||||
		<PluginVersion>10.12.11</PluginVersion>
 | 
			
		||||
		<ProPluginVersion>10.12.11</ProPluginVersion>
 | 
			
		||||
		<DefaultVersion>10.12.11</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.1</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>>
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -131,10 +131,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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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,29 @@ 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)
 | 
			
		||||
    {
 | 
			
		||||
        return SendAsync(this, sendMessage, channel, token);
 | 
			
		||||
 | 
			
		||||
        if (SendDelayTime != 0)
 | 
			
		||||
            await Task.Delay(SendDelayTime, token).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        if (channel is IDtuUdpSessionChannel udpSession)
 | 
			
		||||
        static async PooledTask SendAsync(DeviceBase @this, ISendMessage sendMessage, IClientChannel channel, CancellationToken token)
 | 
			
		||||
        {
 | 
			
		||||
            EndPoint? endPoint = GetUdpEndpoint();
 | 
			
		||||
            await udpSession.SendAsync(endPoint, sendMessage, token).ConfigureAwait(false);
 | 
			
		||||
            if (@this.SendDelayTime != 0)
 | 
			
		||||
                await Task.Delay(@this.SendDelayTime, token).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            await channel.SendAsync(sendMessage, 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 +369,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 +512,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 +544,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 +628,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 +673,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 动态类型读写
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -67,47 +69,52 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
 | 
			
		||||
            _timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(CronScheduledTask)) { 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 +156,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;
 | 
			
		||||
@@ -51,49 +53,54 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
 | 
			
		||||
            _timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(ScheduledAsyncTask)) { 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);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,8 @@ public class ScheduledSyncTask : DisposeBase, IScheduledTask, IScheduledIntInter
 | 
			
		||||
        {
 | 
			
		||||
            if (!Check() && IntervalMS > 8)
 | 
			
		||||
            {
 | 
			
		||||
                SetNext(next);
 | 
			
		||||
                int nextValue = next;
 | 
			
		||||
                SetNext(nextValue);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -122,16 +122,11 @@ public class ControlController : ControllerBase, IRpcServer
 | 
			
		||||
    [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
 | 
			
		||||
    public async Task<Dictionary<string, Dictionary<string, OperResult>>> WriteVariablesAsync([FromBody][TouchSocket.WebApi.FromBody] Dictionary<string, Dictionary<string, string>> deviceDatas)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var deviceData in deviceDatas)
 | 
			
		||||
        {
 | 
			
		||||
            if (GlobalData.Devices.TryGetValue(deviceData.Key, out var device))
 | 
			
		||||
            {
 | 
			
		||||
                var data = device.VariableRuntimes.Where(a => deviceData.Value.ContainsKey(a.Key)).ToList();
 | 
			
		||||
                await GlobalData.SysUserService.CheckApiDataScopeAsync(data.Select(a => a.Value.CreateOrgId), data.Select(a => a.Value.CreateUserId)).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        await GlobalData.CheckByDeviceNames(deviceDatas.Select(a => a.Key)).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        return (await GlobalData.RpcService.InvokeDeviceMethodAsync($"WebApi-{UserManager.UserAccount}-{App.HttpContext?.GetRemoteIpAddressToIPv4()}", deviceDatas).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (OperResult)b.Value));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -12,12 +12,16 @@ using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
 | 
			
		||||
using PooledAwait;
 | 
			
		||||
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Common.Extension;
 | 
			
		||||
using ThingsGateway.Extension.Generic;
 | 
			
		||||
#if !Management
 | 
			
		||||
using ThingsGateway.Gateway.Application.Extensions;
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
using ThingsGateway.NewLife.Json.Extension;
 | 
			
		||||
using ThingsGateway.NewLife.Threading;
 | 
			
		||||
@@ -290,189 +294,198 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    #region 执行方法
 | 
			
		||||
    async ValueTask ReadVariableMed(object? state, CancellationToken cancellationToken)
 | 
			
		||||
    ValueTask ReadVariableMed(object? state, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (state is not VariableMethod readVariableMethods) return;
 | 
			
		||||
        if (state is not VariableMethod readVariableMethods)
 | 
			
		||||
            return ValueTask.CompletedTask;
 | 
			
		||||
        if (Pause)
 | 
			
		||||
            return;
 | 
			
		||||
            return ValueTask.CompletedTask;
 | 
			
		||||
        if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
            return;
 | 
			
		||||
            return ValueTask.CompletedTask;
 | 
			
		||||
        return ReadVariableMed(this, readVariableMethods, cancellationToken);
 | 
			
		||||
 | 
			
		||||
        var readErrorCount = 0;
 | 
			
		||||
 | 
			
		||||
        //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
        //    LogMessage?.Trace(string.Format("{0} - Executing method [{1}]", DeviceName, readVariableMethods.MethodInfo.Name));
 | 
			
		||||
        var readResult = await InvokeMethodAsync(readVariableMethods, cancellationToken: cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        // 方法调用失败时重试一定次数
 | 
			
		||||
        while (!readResult.IsSuccess && readErrorCount < CollectProperties.RetryCount)
 | 
			
		||||
        static async PooledValueTask ReadVariableMed(CollectBase @this, VariableMethod readVariableMethods, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            if (Pause)
 | 
			
		||||
                return;
 | 
			
		||||
            if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            readErrorCount++;
 | 
			
		||||
            if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - failed - {2}", DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage));
 | 
			
		||||
            var readErrorCount = 0;
 | 
			
		||||
 | 
			
		||||
            //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
            //    LogMessage?.Trace(string.Format("{0} - Executing method [{1}]", DeviceName, readVariableMethods.MethodInfo.Name));
 | 
			
		||||
            readResult = await InvokeMethodAsync(readVariableMethods, cancellationToken: cancellationToken).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
            var readResult = await @this.InvokeMethodAsync(readVariableMethods, cancellationToken: cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        if (readResult.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            // 方法调用成功时记录日志并增加成功计数器
 | 
			
		||||
            if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - Succeeded {2}", DeviceName, readVariableMethods.MethodInfo.Name, readResult.Content?.ToSystemTextJsonString()));
 | 
			
		||||
            CurrentDevice.SetDeviceStatus(TimerX.Now, null);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            // 方法调用失败时记录日志并增加失败计数器,更新错误信息
 | 
			
		||||
            if (readVariableMethods.LastErrorMessage != readResult.ErrorMessage)
 | 
			
		||||
            // 方法调用失败时重试一定次数
 | 
			
		||||
            while (!readResult.IsSuccess && readErrorCount < @this.CollectProperties.RetryCount)
 | 
			
		||||
            {
 | 
			
		||||
                if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.MethodFail, DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage));
 | 
			
		||||
                if (@this.Pause)
 | 
			
		||||
                    return;
 | 
			
		||||
                if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                readErrorCount++;
 | 
			
		||||
                if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                    @this.LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - failed - {2}", @this.DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage));
 | 
			
		||||
 | 
			
		||||
                //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                //    LogMessage?.Trace(string.Format("{0} - Executing method [{1}]", DeviceName, readVariableMethods.MethodInfo.Name));
 | 
			
		||||
                readResult = await @this.InvokeMethodAsync(readVariableMethods, cancellationToken: cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (readResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                // 方法调用成功时记录日志并增加成功计数器
 | 
			
		||||
                if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                    @this.LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - Succeeded {2}", @this.DeviceName, readVariableMethods.MethodInfo.Name, readResult.Content?.ToSystemTextJsonString()));
 | 
			
		||||
                @this.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                // 方法调用失败时记录日志并增加失败计数器,更新错误信息
 | 
			
		||||
                if (readVariableMethods.LastErrorMessage != readResult.ErrorMessage)
 | 
			
		||||
                {
 | 
			
		||||
                    if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                        LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - failed - {2}", DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage));
 | 
			
		||||
                    if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                        @this.LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.MethodFail, @this.DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage));
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                            @this.LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - failed - {2}", @this.DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                readVariableMethods.LastErrorMessage = readResult.ErrorMessage;
 | 
			
		||||
                @this.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            readVariableMethods.LastErrorMessage = readResult.ErrorMessage;
 | 
			
		||||
            CurrentDevice.SetDeviceStatus(TimerX.Now, null);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #endregion
 | 
			
		||||
    private readonly LinkedCancellationTokenSourceCache _linkedCtsCache = new();
 | 
			
		||||
 | 
			
		||||
    #region 执行默认读取
 | 
			
		||||
    async ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
 | 
			
		||||
    ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        if (state is not VariableSourceRead variableSourceRead) return;
 | 
			
		||||
 | 
			
		||||
        if (Pause) return;
 | 
			
		||||
        if (cancellationToken.IsCancellationRequested) return;
 | 
			
		||||
        CancellationToken readToken = default;
 | 
			
		||||
        var readerLockTask = ReadWriteLock.ReaderLockAsync(cancellationToken);
 | 
			
		||||
        if (!readerLockTask.IsCompleted)
 | 
			
		||||
        return ReadVariableSource(this, state, cancellationToken);
 | 
			
		||||
        static async PooledValueTask ReadVariableSource(CollectBase @this, object? state, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            readToken = await readerLockTask.ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            readToken = readerLockTask.Result;
 | 
			
		||||
        }
 | 
			
		||||
            if (state is not VariableSourceRead variableSourceRead) return;
 | 
			
		||||
 | 
			
		||||
        if (readToken.IsCancellationRequested)
 | 
			
		||||
        {
 | 
			
		||||
            await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var allTokenSource = _linkedCtsCache.GetLinkedTokenSource(cancellationToken, readToken);
 | 
			
		||||
        var allToken = allTokenSource.Token;
 | 
			
		||||
 | 
			
		||||
        //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
        //    LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
 | 
			
		||||
 | 
			
		||||
        OperResult<ReadOnlyMemory<byte>> readResult = default;
 | 
			
		||||
        var readTask = ReadSourceAsync(variableSourceRead, allToken);
 | 
			
		||||
        if (!readTask.IsCompleted)
 | 
			
		||||
        {
 | 
			
		||||
            readResult = await readTask.ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            readResult = readTask.Result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var readErrorCount = 0;
 | 
			
		||||
 | 
			
		||||
        // 读取失败时重试一定次数
 | 
			
		||||
        while (!readResult.IsSuccess && readErrorCount < CollectProperties.RetryCount)
 | 
			
		||||
        {
 | 
			
		||||
            if (Pause)
 | 
			
		||||
                return;
 | 
			
		||||
            if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
                return;
 | 
			
		||||
            if (@this.Pause) return;
 | 
			
		||||
            if (cancellationToken.IsCancellationRequested) return;
 | 
			
		||||
            CancellationToken readToken = default;
 | 
			
		||||
            var readerLockTask = @this.ReadWriteLock.ReaderLockAsync(cancellationToken);
 | 
			
		||||
            if (!readerLockTask.IsCompletedSuccessfully)
 | 
			
		||||
            {
 | 
			
		||||
                readToken = await readerLockTask.ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                readToken = readerLockTask.Result;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (readToken.IsCancellationRequested)
 | 
			
		||||
            {
 | 
			
		||||
                await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                await @this.ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            readErrorCount++;
 | 
			
		||||
            if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
 | 
			
		||||
            var allTokenSource = @this._linkedCtsCache.GetLinkedTokenSource(cancellationToken, readToken);
 | 
			
		||||
            var allToken = allTokenSource.Token;
 | 
			
		||||
 | 
			
		||||
            //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
            //    LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
 | 
			
		||||
            var readTask1 = ReadSourceAsync(variableSourceRead, allToken);
 | 
			
		||||
            if (!readTask1.IsCompleted)
 | 
			
		||||
 | 
			
		||||
            OperResult<ReadOnlyMemory<byte>> readResult = default;
 | 
			
		||||
            var readTask = @this.ReadSourceAsync(variableSourceRead, allToken);
 | 
			
		||||
            if (!readTask.IsCompletedSuccessfully)
 | 
			
		||||
            {
 | 
			
		||||
                readResult = await readTask1.ConfigureAwait(false);
 | 
			
		||||
                readResult = await readTask.ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                readResult = readTask1.Result;
 | 
			
		||||
                readResult = readTask.Result;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
            var readErrorCount = 0;
 | 
			
		||||
 | 
			
		||||
        if (readResult.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            // 读取成功时记录日志并增加成功计数器
 | 
			
		||||
            if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.Content.Span.ToHexString(' ')));
 | 
			
		||||
            CurrentDevice.SetDeviceStatus(TimerX.Now, null);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
                return;
 | 
			
		||||
            // 读取失败时重试一定次数
 | 
			
		||||
            while (!readResult.IsSuccess && readErrorCount < @this.CollectProperties.RetryCount)
 | 
			
		||||
            {
 | 
			
		||||
                if (@this.Pause)
 | 
			
		||||
                    return;
 | 
			
		||||
                if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
            if (readToken.IsCancellationRequested)
 | 
			
		||||
            {
 | 
			
		||||
                await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 读取失败时记录日志并增加失败计数器,更新错误信息并清除变量状态
 | 
			
		||||
            if (variableSourceRead.LastErrorMessage != readResult.ErrorMessage)
 | 
			
		||||
            {
 | 
			
		||||
                if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.CollectFail, DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                if (readToken.IsCancellationRequested)
 | 
			
		||||
                {
 | 
			
		||||
                    if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                        LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
 | 
			
		||||
                    await @this.ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                readErrorCount++;
 | 
			
		||||
                if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                    @this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
 | 
			
		||||
 | 
			
		||||
                //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                //    LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
 | 
			
		||||
                var readTask1 = @this.ReadSourceAsync(variableSourceRead, allToken);
 | 
			
		||||
                if (!readTask1.IsCompletedSuccessfully)
 | 
			
		||||
                {
 | 
			
		||||
                    readResult = await readTask1.ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    readResult = readTask1.Result;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            variableSourceRead.LastErrorMessage = readResult.ErrorMessage;
 | 
			
		||||
            CurrentDevice.SetDeviceStatus(TimerX.Now, null, readResult.ErrorMessage);
 | 
			
		||||
            var time = DateTime.Now;
 | 
			
		||||
            foreach (var item in variableSourceRead.VariableRuntimes)
 | 
			
		||||
            if (readResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                item.SetValue(null, time, isOnline: false);
 | 
			
		||||
                // 读取成功时记录日志并增加成功计数器
 | 
			
		||||
                if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                    @this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.Content.Span.ToHexString(' ')));
 | 
			
		||||
                @this.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                if (readToken.IsCancellationRequested)
 | 
			
		||||
                {
 | 
			
		||||
                    await @this.ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // 读取失败时记录日志并增加失败计数器,更新错误信息并清除变量状态
 | 
			
		||||
                if (variableSourceRead.LastErrorMessage != readResult.ErrorMessage)
 | 
			
		||||
                {
 | 
			
		||||
                    if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                        @this.LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.CollectFail, @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
 | 
			
		||||
                            @this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                variableSourceRead.LastErrorMessage = readResult.ErrorMessage;
 | 
			
		||||
                @this.CurrentDevice.SetDeviceStatus(TimerX.Now, null, readResult.ErrorMessage);
 | 
			
		||||
                var time = DateTime.Now;
 | 
			
		||||
                foreach (var item in variableSourceRead.VariableRuntimes)
 | 
			
		||||
                {
 | 
			
		||||
                    item.SetValue(null, time, isOnline: false);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -480,9 +493,9 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    protected virtual Task TestOnline(object? state, CancellationToken cancellationToken)
 | 
			
		||||
    protected virtual ValueTask TestOnline(object? state, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
        return ValueTask.CompletedTask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void ScriptVariableRun(object? state, CancellationToken cancellationToken)
 | 
			
		||||
@@ -533,38 +546,43 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
    {
 | 
			
		||||
        throw new NotImplementedException();
 | 
			
		||||
    }
 | 
			
		||||
    protected async Task Check(Dictionary<VariableRuntime, JToken> writeInfoLists, NonBlockingDictionary<string, OperResult> operResults, CancellationToken cancellationToken)
 | 
			
		||||
    protected Task Check(Dictionary<VariableRuntime, JToken> writeInfoLists, NonBlockingDictionary<string, OperResult> operResults, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (VariableSourceReadsEnable)
 | 
			
		||||
        return Check(this, writeInfoLists, operResults, cancellationToken);
 | 
			
		||||
 | 
			
		||||
        static async PooledTask Check(CollectBase @this, Dictionary<VariableRuntime, JToken> writeInfoLists, NonBlockingDictionary<string, OperResult> operResults, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            // 如果成功,每个变量都读取一次最新值,再次比较写入值
 | 
			
		||||
            var successfulWriteNames = operResults.Where(a => a.Value.IsSuccess).Select(a => a.Key).ToHashSet();
 | 
			
		||||
 | 
			
		||||
            var groups = writeInfoLists.Select(a => a.Key).Where(a => a.RpcWriteCheck && a.ProtectType != ProtectTypeEnum.WriteOnly && successfulWriteNames.Contains(a.Name) && a.VariableSource != null).GroupBy(a => a.VariableSource as VariableSourceRead).Where(a => a.Key != null).ToArray();
 | 
			
		||||
 | 
			
		||||
            await groups.ParallelForEachAsync(async (varRead, token) =>
 | 
			
		||||
            if (@this.VariableSourceReadsEnable)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await ReadSourceAsync(varRead.Key, token).ConfigureAwait(false);
 | 
			
		||||
                if (result.IsSuccess)
 | 
			
		||||
                // 如果成功,每个变量都读取一次最新值,再次比较写入值
 | 
			
		||||
                var successfulWriteNames = operResults.Where(a => a.Value.IsSuccess).Select(a => a.Key).ToHashSet();
 | 
			
		||||
 | 
			
		||||
                var groups = writeInfoLists.Select(a => a.Key).Where(a => a.RpcWriteCheck && a.ProtectType != ProtectTypeEnum.WriteOnly && successfulWriteNames.Contains(a.Name) && a.VariableSource != null).GroupBy(a => a.VariableSource as VariableSourceRead).Where(a => a.Key != null).ToArray();
 | 
			
		||||
 | 
			
		||||
                await groups.ParallelForEachAsync(async (varRead, token) =>
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var item in varRead)
 | 
			
		||||
                    var result = await @this.ReadSourceAsync(varRead.Key, token).ConfigureAwait(false);
 | 
			
		||||
                    if (result.IsSuccess)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (!item.Value.Equals(writeInfoLists[item].ToObject(item.Value?.GetType())))
 | 
			
		||||
                        foreach (var item in varRead)
 | 
			
		||||
                        {
 | 
			
		||||
                            // 如果写入值与读取值不同,则更新操作结果为失败
 | 
			
		||||
                            operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value,  Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}");
 | 
			
		||||
                            if (!item.Value.Equals(writeInfoLists[item].ToObject(item.Value?.GetType())))
 | 
			
		||||
                            {
 | 
			
		||||
                                // 如果写入值与读取值不同,则更新操作结果为失败
 | 
			
		||||
                                operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value,  Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}");
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var item in varRead)
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        // 如果写入值与读取值不同,则更新操作结果为失败
 | 
			
		||||
                        operResults[item.Name] = new OperResult($"Reading and rechecking resulted in an error: {result.ErrorMessage}", result.Exception);
 | 
			
		||||
                        foreach (var item in varRead)
 | 
			
		||||
                        {
 | 
			
		||||
                            // 如果写入值与读取值不同,则更新操作结果为失败
 | 
			
		||||
                            operResults[item.Name] = new OperResult($"Reading and rechecking resulted in an error: {result.ErrorMessage}", result.Exception);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                }, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -576,66 +594,71 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
    /// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
 | 
			
		||||
    /// <param name="cancellationToken">取消操作的通知</param>
 | 
			
		||||
    /// <returns>写入操作的结果字典</returns>
 | 
			
		||||
    public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
 | 
			
		||||
    public ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        // 初始化结果字典
 | 
			
		||||
        Dictionary<string, OperResult<object>> results = new Dictionary<string, OperResult<object>>();
 | 
			
		||||
        return InvokeMethodAsync(this, writeInfoLists, cancellationToken);
 | 
			
		||||
 | 
			
		||||
        // 遍历写入信息列表
 | 
			
		||||
        foreach (var (deviceVariable, jToken) in writeInfoLists)
 | 
			
		||||
        static async PooledValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(CollectBase @this, Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            // 检查是否有写入表达式
 | 
			
		||||
            if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions))
 | 
			
		||||
            // 初始化结果字典
 | 
			
		||||
            Dictionary<string, OperResult<object>> results = new Dictionary<string, OperResult<object>>();
 | 
			
		||||
 | 
			
		||||
            // 遍历写入信息列表
 | 
			
		||||
            foreach (var (deviceVariable, jToken) in writeInfoLists)
 | 
			
		||||
            {
 | 
			
		||||
                // 检查是否有写入表达式
 | 
			
		||||
                if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions))
 | 
			
		||||
                {
 | 
			
		||||
                    // 提取原始数据
 | 
			
		||||
                    object rawdata = jToken.GetObjectFromJToken();
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        // 根据写入表达式转换数据
 | 
			
		||||
                        object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, @this.LogMessage);
 | 
			
		||||
                        // 将转换后的数据重新赋值给写入信息列表
 | 
			
		||||
                        writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data);
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        // 如果转换失败,则记录错误信息
 | 
			
		||||
                        results.Add(deviceVariable.Name, new OperResult<object>(string.Format(AppResource.WriteExpressionsError, deviceVariable.Name, deviceVariable.WriteExpressions, ex.Message), ex));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            NonBlockingDictionary<string, OperResult<object>> operResults = new();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            using var writeLock = await @this.ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            var list = writeInfoLists
 | 
			
		||||
            .Where(a => !results.Any(b => b.Key == a.Key.Name))
 | 
			
		||||
            .ToArray();
 | 
			
		||||
            // 使用并发方式遍历写入信息列表,并进行异步写入操作
 | 
			
		||||
            await list.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
 | 
			
		||||
            {
 | 
			
		||||
                // 提取原始数据
 | 
			
		||||
                object rawdata = jToken.GetObjectFromJToken();
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    // 根据写入表达式转换数据
 | 
			
		||||
                    object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, LogMessage);
 | 
			
		||||
                    // 将转换后的数据重新赋值给写入信息列表
 | 
			
		||||
                    writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data);
 | 
			
		||||
                    // 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果
 | 
			
		||||
                    var result = await @this.InvokeMethodAsync(writeInfo.Key.VariableMethod, writeInfo.Value?.ToString(), false, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    // 将操作结果添加到结果字典中,使用变量名称作为键
 | 
			
		||||
                    operResults.TryAdd(writeInfo.Key.Name, result);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    // 如果转换失败,则记录错误信息
 | 
			
		||||
                    results.Add(deviceVariable.Name, new OperResult<object>(string.Format(AppResource.WriteExpressionsError, deviceVariable.Name, deviceVariable.WriteExpressions, ex.Message), ex));
 | 
			
		||||
                    operResults.TryAdd(writeInfo.Key.Name, new(ex));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
            }, @this.CollectProperties.MaxConcurrentCount, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        NonBlockingDictionary<string, OperResult<object>> operResults = new();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
 | 
			
		||||
        var list = writeInfoLists
 | 
			
		||||
        .Where(a => !results.Any(b => b.Key == a.Key.Name))
 | 
			
		||||
        .ToArray();
 | 
			
		||||
        // 使用并发方式遍历写入信息列表,并进行异步写入操作
 | 
			
		||||
        await list.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                // 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果
 | 
			
		||||
                var result = await InvokeMethodAsync(writeInfo.Key.VariableMethod, writeInfo.Value?.ToString(), false, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                // 将操作结果添加到结果字典中,使用变量名称作为键
 | 
			
		||||
                operResults.TryAdd(writeInfo.Key.Name, result);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                operResults.TryAdd(writeInfo.Key.Name, new(ex));
 | 
			
		||||
            }
 | 
			
		||||
        }, CollectProperties.MaxConcurrentCount, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        // 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中
 | 
			
		||||
        return new Dictionary<string, Dictionary<string, IOperResult>>()
 | 
			
		||||
            // 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中
 | 
			
		||||
            return new Dictionary<string, Dictionary<string, IOperResult>>()
 | 
			
		||||
        {
 | 
			
		||||
            {
 | 
			
		||||
             DeviceName ,
 | 
			
		||||
             @this.DeviceName ,
 | 
			
		||||
             results.Concat(operResults).ToDictionary(a => a.Key, a => (IOperResult)a.Value)
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -644,58 +667,63 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
    /// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
 | 
			
		||||
    /// <param name="cancellationToken">取消操作的通知</param>
 | 
			
		||||
    /// <returns>写入操作的结果字典</returns>
 | 
			
		||||
    public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
 | 
			
		||||
    public ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        // 初始化结果字典
 | 
			
		||||
        Dictionary<string, OperResult> results = new Dictionary<string, OperResult>();
 | 
			
		||||
        return InVokeWriteAsync(this, writeInfoLists, cancellationToken);
 | 
			
		||||
 | 
			
		||||
        // 遍历写入信息列表
 | 
			
		||||
        foreach (var (deviceVariable, jToken) in writeInfoLists)
 | 
			
		||||
        static async PooledValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(CollectBase @this, Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            // 检查是否有写入表达式
 | 
			
		||||
            if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions))
 | 
			
		||||
            // 初始化结果字典
 | 
			
		||||
            Dictionary<string, OperResult> results = new Dictionary<string, OperResult>();
 | 
			
		||||
 | 
			
		||||
            // 遍历写入信息列表
 | 
			
		||||
            foreach (var (deviceVariable, jToken) in writeInfoLists)
 | 
			
		||||
            {
 | 
			
		||||
                // 提取原始数据
 | 
			
		||||
                object rawdata = jToken.GetObjectFromJToken();
 | 
			
		||||
                try
 | 
			
		||||
                // 检查是否有写入表达式
 | 
			
		||||
                if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions))
 | 
			
		||||
                {
 | 
			
		||||
                    // 根据写入表达式转换数据
 | 
			
		||||
                    object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, LogMessage);
 | 
			
		||||
                    // 将转换后的数据重新赋值给写入信息列表
 | 
			
		||||
                    writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    // 如果转换失败,则记录错误信息
 | 
			
		||||
                    results.Add(deviceVariable.Name, new OperResult(string.Format(AppResource.WriteExpressionsError, deviceVariable.Name, deviceVariable.WriteExpressions, ex.Message), ex));
 | 
			
		||||
                    // 提取原始数据
 | 
			
		||||
                    object rawdata = jToken.GetObjectFromJToken();
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        // 根据写入表达式转换数据
 | 
			
		||||
                        object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, @this.LogMessage);
 | 
			
		||||
                        // 将转换后的数据重新赋值给写入信息列表
 | 
			
		||||
                        writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data);
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        // 如果转换失败,则记录错误信息
 | 
			
		||||
                        results.Add(deviceVariable.Name, new OperResult(string.Format(AppResource.WriteExpressionsError, deviceVariable.Name, deviceVariable.WriteExpressions, ex.Message), ex));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var writePList = writeInfoLists.Where(a => !CurrentDevice.VariableScriptReads.Select(a => a.VariableRuntime).Any(b => a.Key.Name == b.Name));
 | 
			
		||||
        var writeSList = writeInfoLists.Where(a => CurrentDevice.VariableScriptReads.Select(a => a.VariableRuntime).Any(b => a.Key.Name == b.Name));
 | 
			
		||||
            var writePList = writeInfoLists.Where(a => !@this.CurrentDevice.VariableScriptReads.Select(a => a.VariableRuntime).Any(b => a.Key.Name == b.Name));
 | 
			
		||||
            var writeSList = writeInfoLists.Where(a => @this.CurrentDevice.VariableScriptReads.Select(a => a.VariableRuntime).Any(b => a.Key.Name == b.Name));
 | 
			
		||||
 | 
			
		||||
        DateTime now = DateTime.Now;
 | 
			
		||||
        foreach (var item in writeSList)
 | 
			
		||||
        {
 | 
			
		||||
            results.TryAdd(item.Key.Name, item.Key.SetValue(item.Value, now));
 | 
			
		||||
        }
 | 
			
		||||
            DateTime now = DateTime.Now;
 | 
			
		||||
            foreach (var item in writeSList)
 | 
			
		||||
            {
 | 
			
		||||
                results.TryAdd(item.Key.Name, item.Key.SetValue(item.Value, now));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        // 过滤掉转换失败的变量,只保留写入成功的变量进行写入操作
 | 
			
		||||
        var results1 = await WriteValuesAsync(writePList
 | 
			
		||||
            .Where(a => !results.Any(b => b.Key == a.Key.Name))
 | 
			
		||||
            .ToDictionary(item => item.Key, item => item.Value),
 | 
			
		||||
            cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            // 过滤掉转换失败的变量,只保留写入成功的变量进行写入操作
 | 
			
		||||
            var results1 = await @this.WriteValuesAsync(writePList
 | 
			
		||||
                .Where(a => !results.Any(b => b.Key == a.Key.Name))
 | 
			
		||||
                .ToDictionary(item => item.Key, item => item.Value),
 | 
			
		||||
                cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        // 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中
 | 
			
		||||
            // 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中
 | 
			
		||||
 | 
			
		||||
        return new Dictionary<string, Dictionary<string, IOperResult>>()
 | 
			
		||||
            return new Dictionary<string, Dictionary<string, IOperResult>>()
 | 
			
		||||
        {
 | 
			
		||||
            {
 | 
			
		||||
                DeviceName ,
 | 
			
		||||
               @this. DeviceName ,
 | 
			
		||||
                results.Concat(results1).ToDictionary(a => a.Key, a => (IOperResult)a.Value)
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -706,56 +734,61 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
    /// <param name="isRead">指示是否为读取操作</param>
 | 
			
		||||
    /// <param name="cancellationToken">取消操作的通知</param>
 | 
			
		||||
    /// <returns>操作结果,包含执行方法的结果</returns>
 | 
			
		||||
    protected virtual async ValueTask<OperResult<object>> InvokeMethodAsync(VariableMethod variableMethod, string? value = null, bool isRead = true, CancellationToken cancellationToken = default)
 | 
			
		||||
    protected virtual ValueTask<OperResult<object>> InvokeMethodAsync(VariableMethod variableMethod, string? value = null, bool isRead = true, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        return InvokeMethodAsync(this, variableMethod, value, isRead, cancellationToken);
 | 
			
		||||
 | 
			
		||||
        static async PooledValueTask<OperResult<object>> InvokeMethodAsync(CollectBase @this, VariableMethod variableMethod, string? value, bool isRead, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            // 初始化操作结果
 | 
			
		||||
            OperResult<object> result = new OperResult<object>();
 | 
			
		||||
 | 
			
		||||
            // 获取要执行的方法
 | 
			
		||||
            var method = variableMethod.MethodInfo;
 | 
			
		||||
 | 
			
		||||
            // 如果方法未找到,则返回错误结果
 | 
			
		||||
            if (method == null)
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                result.OperCode = 999;
 | 
			
		||||
                result.ErrorMessage = string.Format(AppResource.MethodNotNull, variableMethod.Variable.Name, variableMethod.Variable.OtherMethod);
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                // 调用方法并获取结果
 | 
			
		||||
                var data = await variableMethod.InvokeMethodAsync(this, value, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                // 初始化操作结果
 | 
			
		||||
                OperResult<object> result = new OperResult<object>();
 | 
			
		||||
 | 
			
		||||
                result = data.GetOperResult();
 | 
			
		||||
                // 获取要执行的方法
 | 
			
		||||
                var method = variableMethod.MethodInfo;
 | 
			
		||||
 | 
			
		||||
                // 如果方法有返回值,并且是读取操作
 | 
			
		||||
                if (method.HasReturn && isRead)
 | 
			
		||||
                // 如果方法未找到,则返回错误结果
 | 
			
		||||
                if (method == null)
 | 
			
		||||
                {
 | 
			
		||||
                    var time = DateTime.Now;
 | 
			
		||||
                    if (result.IsSuccess == true)
 | 
			
		||||
                    {
 | 
			
		||||
                        // 将结果序列化并设置到变量中
 | 
			
		||||
                        var variableResult = variableMethod.Variable.SetValue(result.Content, time);
 | 
			
		||||
                        if (!variableResult.IsSuccess)
 | 
			
		||||
                            variableMethod.LastErrorMessage = result.ErrorMessage;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        // 如果读取操作失败,则将变量标记为离线
 | 
			
		||||
                        var variableResult = variableMethod.Variable.SetValue(null, time, isOnline: false);
 | 
			
		||||
                        if (!variableResult.IsSuccess)
 | 
			
		||||
                            variableMethod.LastErrorMessage = result.ErrorMessage;
 | 
			
		||||
                    }
 | 
			
		||||
                    result.OperCode = 999;
 | 
			
		||||
                    result.ErrorMessage = string.Format(AppResource.MethodNotNull, variableMethod.Variable.Name, variableMethod.Variable.OtherMethod);
 | 
			
		||||
                    return result;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    // 调用方法并获取结果
 | 
			
		||||
                    var data = await variableMethod.InvokeMethodAsync(@this, value, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    result = data.GetOperResult();
 | 
			
		||||
 | 
			
		||||
                    // 如果方法有返回值,并且是读取操作
 | 
			
		||||
                    if (method.HasReturn && isRead)
 | 
			
		||||
                    {
 | 
			
		||||
                        var time = DateTime.Now;
 | 
			
		||||
                        if (result.IsSuccess == true)
 | 
			
		||||
                        {
 | 
			
		||||
                            // 将结果序列化并设置到变量中
 | 
			
		||||
                            var variableResult = variableMethod.Variable.SetValue(result.Content, time);
 | 
			
		||||
                            if (!variableResult.IsSuccess)
 | 
			
		||||
                                variableMethod.LastErrorMessage = result.ErrorMessage;
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            // 如果读取操作失败,则将变量标记为离线
 | 
			
		||||
                            var variableResult = variableMethod.Variable.SetValue(null, time, isOnline: false);
 | 
			
		||||
                            if (!variableResult.IsSuccess)
 | 
			
		||||
                                variableMethod.LastErrorMessage = result.ErrorMessage;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    return result;
 | 
			
		||||
                }
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            // 捕获异常并返回错误结果
 | 
			
		||||
            return new OperResult<object>(ex);
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                // 捕获异常并返回错误结果
 | 
			
		||||
                return new OperResult<object>(ex);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,8 @@ using Microsoft.Extensions.Logging;
 | 
			
		||||
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
 | 
			
		||||
using PooledAwait;
 | 
			
		||||
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Common.Extension;
 | 
			
		||||
@@ -79,158 +81,174 @@ public abstract class CollectFoundationBase : CollectBase
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    protected override async Task TestOnline(object? state, CancellationToken cancellationToken)
 | 
			
		||||
    protected override ValueTask TestOnline(object? state, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (FoundationDevice != null)
 | 
			
		||||
        return TestOnline(this, cancellationToken);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        static async PooledValueTask TestOnline(CollectFoundationBase @this, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            if (!FoundationDevice.OnLine)
 | 
			
		||||
            if (@this.FoundationDevice != null)
 | 
			
		||||
            {
 | 
			
		||||
                if (!FoundationDevice.DisposedValue || FoundationDevice.Channel?.DisposedValue != false) return;
 | 
			
		||||
                Exception exception = null;
 | 
			
		||||
                try
 | 
			
		||||
                if (!@this.FoundationDevice.OnLine)
 | 
			
		||||
                {
 | 
			
		||||
                    if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    if (!@this.FoundationDevice.DisposedValue || @this.FoundationDevice.Channel?.DisposedValue != false) return;
 | 
			
		||||
                    Exception exception = null;
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        if (!FoundationDevice.DisposedValue || FoundationDevice.Channel?.DisposedValue != false) return;
 | 
			
		||||
 | 
			
		||||
                        await FoundationDevice.ConnectAsync(cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                        if (CurrentDevice.DeviceStatusChangeTime < TimerX.Now.AddMinutes(-1))
 | 
			
		||||
                        if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                        {
 | 
			
		||||
                            await Task.Delay(30000, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                            if (!@this.FoundationDevice.DisposedValue || @this.FoundationDevice.Channel?.DisposedValue != false) return;
 | 
			
		||||
 | 
			
		||||
                            await @this.FoundationDevice.ConnectAsync(cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                            if (@this.CurrentDevice.DeviceStatusChangeTime < TimerX.Now.AddMinutes(-1))
 | 
			
		||||
                            {
 | 
			
		||||
                                await Task.Delay(30000, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (OperationCanceledException)
 | 
			
		||||
                {
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    exception = ex;
 | 
			
		||||
                }
 | 
			
		||||
                if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
                {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                if (FoundationDevice.OnLine == false && exception != null)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var item in CurrentDevice.VariableSourceReads)
 | 
			
		||||
                    catch (OperationCanceledException)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (item.LastErrorMessage != exception.Message)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                                LogMessage?.LogWarning(exception, string.Format(AppResource.CollectFail, DeviceName, item?.RegisterAddress, item?.Length, exception.Message));
 | 
			
		||||
                        }
 | 
			
		||||
                        item.LastErrorMessage = exception.Message;
 | 
			
		||||
                        CurrentDevice.SetDeviceStatus(TimerX.Now, null, exception.Message);
 | 
			
		||||
                        var time = DateTime.Now;
 | 
			
		||||
                        item.VariableRuntimes.ForEach(a => a.SetValue(null, time, isOnline: false));
 | 
			
		||||
                    }
 | 
			
		||||
                    foreach (var item in CurrentDevice.ReadVariableMethods)
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (item.LastErrorMessage != exception.Message)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                                LogMessage?.LogWarning(exception, string.Format(AppResource.MethodFail, DeviceName, item.MethodInfo.Name, exception.Message));
 | 
			
		||||
                        }
 | 
			
		||||
                        item.LastErrorMessage = exception.Message;
 | 
			
		||||
                        CurrentDevice.SetDeviceStatus(TimerX.Now, null, exception.Message);
 | 
			
		||||
                        var time = DateTime.Now;
 | 
			
		||||
                        item.Variable.SetValue(null, time, isOnline: false);
 | 
			
		||||
                        exception = ex;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (@this.FoundationDevice.OnLine == false && exception != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        foreach (var item in @this.CurrentDevice.VariableSourceReads)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (item.LastErrorMessage != exception.Message)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                                    @this.LogMessage?.LogWarning(exception, string.Format(AppResource.CollectFail, @this.DeviceName, item?.RegisterAddress, item?.Length, exception.Message));
 | 
			
		||||
                            }
 | 
			
		||||
                            item.LastErrorMessage = exception.Message;
 | 
			
		||||
                            @this.CurrentDevice.SetDeviceStatus(TimerX.Now, null, exception.Message);
 | 
			
		||||
                            var time = DateTime.Now;
 | 
			
		||||
                            item.VariableRuntimes.ForEach(a => a.SetValue(null, time, isOnline: false));
 | 
			
		||||
                        }
 | 
			
		||||
                        foreach (var item in @this.CurrentDevice.ReadVariableMethods)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (item.LastErrorMessage != exception.Message)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (!cancellationToken.IsCancellationRequested)
 | 
			
		||||
                                    @this.LogMessage?.LogWarning(exception, string.Format(AppResource.MethodFail, @this.DeviceName, item.MethodInfo.Name, exception.Message));
 | 
			
		||||
                            }
 | 
			
		||||
                            item.LastErrorMessage = exception.Message;
 | 
			
		||||
                            @this.CurrentDevice.SetDeviceStatus(TimerX.Now, null, exception.Message);
 | 
			
		||||
                            var time = DateTime.Now;
 | 
			
		||||
                            item.Variable.SetValue(null, time, isOnline: false);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 采集驱动读取,读取成功后直接赋值变量,失败不做处理,注意非通用设备需重写
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    protected override async ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    protected override ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        return ReadSourceAsync(this, variableSourceRead, cancellationToken);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        static async PooledValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(CollectFoundationBase @this, VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
                return new(new OperationCanceledException());
 | 
			
		||||
 | 
			
		||||
            // 从协议读取数据
 | 
			
		||||
            OperResult<ReadOnlyMemory<byte>> read = default;
 | 
			
		||||
            var readTask = FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken);
 | 
			
		||||
            if (!readTask.IsCompleted)
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                read = await readTask.ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                read = readTask.Result;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 如果读取成功且有有效内容,则解析结构化内容
 | 
			
		||||
            if (read.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var prase = variableSourceRead.VariableRuntimes.PraseStructContent(FoundationDevice, read.Content.Span, false);
 | 
			
		||||
                return new OperResult<ReadOnlyMemory<byte>>(prase);
 | 
			
		||||
            }
 | 
			
		||||
                if (cancellationToken.IsCancellationRequested)
 | 
			
		||||
                    return new(new OperationCanceledException());
 | 
			
		||||
 | 
			
		||||
            // 返回读取结果
 | 
			
		||||
            return read;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            // 捕获异常并返回失败结果
 | 
			
		||||
            return new OperResult<ReadOnlyMemory<byte>>(ex);
 | 
			
		||||
                // 从协议读取数据
 | 
			
		||||
                OperResult<ReadOnlyMemory<byte>> read = default;
 | 
			
		||||
                var readTask = @this.FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken);
 | 
			
		||||
                if (!readTask.IsCompletedSuccessfully)
 | 
			
		||||
                {
 | 
			
		||||
                    read = await readTask.ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    read = readTask.Result;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // 如果读取成功且有有效内容,则解析结构化内容
 | 
			
		||||
                if (read.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    var prase = variableSourceRead.VariableRuntimes.PraseStructContent(@this.FoundationDevice, read.Content.Span, false);
 | 
			
		||||
                    return new OperResult<ReadOnlyMemory<byte>>(prase);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // 返回读取结果
 | 
			
		||||
                return read;
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                // 捕获异常并返回失败结果
 | 
			
		||||
                return new OperResult<ReadOnlyMemory<byte>>(ex);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 批量写入变量值,需返回变量名称/结果,注意非通用设备需重写
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
 | 
			
		||||
    protected override ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
 | 
			
		||||
        // 检查协议是否为空,如果为空则抛出异常
 | 
			
		||||
        if (FoundationDevice == null)
 | 
			
		||||
            throw new NotSupportedException();
 | 
			
		||||
        return WriteValuesAsync(this, writeInfoLists, cancellationToken);
 | 
			
		||||
 | 
			
		||||
        // 创建用于存储操作结果的并发字典
 | 
			
		||||
        NonBlockingDictionary<string, OperResult> operResults = new();
 | 
			
		||||
        // 使用并发方式遍历写入信息列表,并进行异步写入操作
 | 
			
		||||
        await writeInfoLists.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
 | 
			
		||||
        static async PooledValueTask<Dictionary<string, OperResult>> WriteValuesAsync(CollectFoundationBase @this, Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            using var writeLock = await @this.ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            // 检查协议是否为空,如果为空则抛出异常
 | 
			
		||||
            if (@this.FoundationDevice == null)
 | 
			
		||||
                throw new NotSupportedException();
 | 
			
		||||
 | 
			
		||||
            // 创建用于存储操作结果的并发字典
 | 
			
		||||
            NonBlockingDictionary<string, OperResult> operResults = new();
 | 
			
		||||
            // 使用并发方式遍历写入信息列表,并进行异步写入操作
 | 
			
		||||
            await writeInfoLists.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                // 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果
 | 
			
		||||
                var result = await FoundationDevice.WriteJTokenAsync(writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                if (result.IsSuccess)
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug)
 | 
			
		||||
                        LogMessage?.Debug(string.Format("{0} - Write [{1} - {2} - {3}] data succeeded", DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType));
 | 
			
		||||
 | 
			
		||||
                    // 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果
 | 
			
		||||
                    var result = await @this.FoundationDevice.WriteJTokenAsync(writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    if (result.IsSuccess)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug)
 | 
			
		||||
                            @this.LogMessage?.Debug(string.Format("{0} - Write [{1} - {2} - {3}] data succeeded", @this.DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType));
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        @this.LogMessage?.Warning(string.Format("{0} - Write [{1} - {2} - {3}] data failed {4}", @this.DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, result.ToString()));
 | 
			
		||||
                    }
 | 
			
		||||
                    // 将操作结果添加到结果字典中,使用变量名称作为键
 | 
			
		||||
                    operResults.TryAdd(writeInfo.Key.Name, result);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    LogMessage?.Warning(string.Format("{0} - Write [{1} - {2} - {3}] data failed {4}", DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, result.ToString()));
 | 
			
		||||
                    operResults.TryAdd(writeInfo.Key.Name, new(ex));
 | 
			
		||||
                }
 | 
			
		||||
                // 将操作结果添加到结果字典中,使用变量名称作为键
 | 
			
		||||
                operResults.TryAdd(writeInfo.Key.Name, result);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                operResults.TryAdd(writeInfo.Key.Name, new(ex));
 | 
			
		||||
            }
 | 
			
		||||
        }, CollectProperties.MaxConcurrentCount, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            }, @this.CollectProperties.MaxConcurrentCount, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        await Check(writeInfoLists, operResults, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            await @this.Check(writeInfoLists, operResults, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        // 返回包含操作结果的字典
 | 
			
		||||
        return new Dictionary<string, OperResult>(operResults);
 | 
			
		||||
            // 返回包含操作结果的字典
 | 
			
		||||
            return new Dictionary<string, OperResult>(operResults);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ namespace ThingsGateway.Gateway.Application;
 | 
			
		||||
[SugarIndex("index_device", nameof(Variable.DeviceId), OrderByType.Asc)]
 | 
			
		||||
[SugarIndex("unique_deviceid_variable_name", nameof(Variable.Name), OrderByType.Asc, nameof(Variable.DeviceId), OrderByType.Asc, true)]
 | 
			
		||||
#endif
 | 
			
		||||
public class Variable : BaseDataEntity, IValidatableObject
 | 
			
		||||
public class Variable : PrimaryKeyEntity, IValidatableObject
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 主键Id
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user