mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 23:53:58 +08:00 
			
		
		
		
	Compare commits
	
		
			37 Commits
		
	
	
		
			10.11.94.0
			...
			10.12.4.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 96b4287f3a | ||
|   | 7d406de29f | ||
|   | 81f0ef466a | ||
|   | 3f2d6b133c | ||
|   | e776dc67eb | ||
| ![devin-ai-integration[bot]](/assets/img/avatar_default.png)  | bc5827d140 | ||
|   | 21838bf4af | ||
|   | 6090108597 | ||
|   | b47b9e6f43 | ||
|   | 18d1cffb2d | ||
|   | 516fd7f235 | ||
|   | 2ee16c3533 | ||
|   | 7d22f5c78e | ||
|   | 3e604ee2fd | ||
|   | 47e442874c | ||
|   | 2a8c0cbab1 | ||
|   | c26898b49d | ||
|   | 00c24d06a3 | ||
|   | 3461f34240 | ||
|   | aa1ce08c02 | ||
|   | 9c230c2da9 | ||
|   | 21215d0379 | ||
|   | 7448183791 | ||
|   | 35edd7dc43 | ||
|   | bd178831e3 | ||
|   | fe9ec6ad10 | ||
|   | 6f9ec2e24b | ||
|   | c0337e2b19 | ||
|   | 8a95f48f5a | ||
|   | 14f3c31265 | ||
|   | 1bad65378f | ||
|   | db3affc67e | ||
|   | 5ee8b50a92 | ||
|   | 301beda2a2 | ||
|   | 628b51a353 | ||
|   | f03445bc83 | ||
|   | 55a2ff5487 | 
| @@ -251,11 +251,13 @@ public class RequestAuditFilter : IAsyncActionFilter, IOrderedFilter | |||||||
|  |  | ||||||
|         if (exception == null) |         if (exception == null) | ||||||
|         { |         { | ||||||
|             logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}"); |             if (logger.IsEnabled(LogLevel.Information)) | ||||||
|  |                 logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}"); | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|             logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}"); |             if (logger.IsEnabled(LogLevel.Warning)) | ||||||
|  |                 logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ namespace ThingsGateway.Admin.Application; | |||||||
| /// <typeparam name="TEntry"></typeparam> | /// <typeparam name="TEntry"></typeparam> | ||||||
| public class EventService<TEntry> : IEventService<TEntry>, IDisposable | public class EventService<TEntry> : IEventService<TEntry>, IDisposable | ||||||
| { | { | ||||||
|     private ConcurrentDictionary<string, Func<TEntry, Task>> Cache = new(); |     private NonBlockingDictionary<string, Func<TEntry, Task>> Cache = new(); | ||||||
|  |  | ||||||
|     public void Dispose() |     public void Dispose() | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -19,12 +19,14 @@ | |||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| 	 | 	 | ||||||
| 	<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"> | ||||||
| 		<PackageReference Include="Rougamo.Fody" Version="5.0.1" /> | 		  <IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||||
|  | 		</PackageReference> | ||||||
|  | 		<PackageReference Include="Rougamo.Fody" Version="5.0.2" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> | 	<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> | ||||||
| 		<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" /> | 		<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" /> | 		<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" /> | ||||||
|  |  | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|   | |||||||
| @@ -4,8 +4,8 @@ | |||||||
|  |  | ||||||
| <div class="tg-table h-100"> | <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" |     <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!" |            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!" |            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 |            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 |            ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton | ||||||
| @@ -14,7 +14,7 @@ | |||||||
|            ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo |            ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo | ||||||
|            SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText |            SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText | ||||||
|            ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode |            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 |            FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn | ||||||
|            IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval |            IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval | ||||||
|            AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh |            AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh | ||||||
| @@ -41,6 +41,7 @@ | |||||||
|            DoubleClickToEdit="DoubleClickToEdit" |            DoubleClickToEdit="DoubleClickToEdit" | ||||||
|            OnDoubleClickCellCallback="OnDoubleClickCellCallback" |            OnDoubleClickCellCallback="OnDoubleClickCellCallback" | ||||||
|            OnDoubleClickRowCallback="OnDoubleClickRowCallback" |            OnDoubleClickRowCallback="OnDoubleClickRowCallback" | ||||||
|  |            RowContentTemplate="RowContentTemplate" | ||||||
|            OnClickRowCallback="OnClickRowCallback"> |            OnClickRowCallback="OnClickRowCallback"> | ||||||
|     </Table> |     </Table> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -13,6 +13,23 @@ namespace ThingsGateway.Admin.Razor; | |||||||
| [CascadingTypeParameter(nameof(TItem))] | [CascadingTypeParameter(nameof(TItem))] | ||||||
| public partial class AdminTable<TItem> where TItem : class, new() | 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"/> |     /// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public EventCallback<List<TItem>> SelectedRowsChanged { get; set; } |     public EventCallback<List<TItem>> SelectedRowsChanged { get; set; } | ||||||
| @@ -40,6 +57,10 @@ public partial class AdminTable<TItem> where TItem : class, new() | |||||||
|     /// <inheritdoc cref="Table{TItem}.OnDoubleClickRowCallback"/> |     /// <inheritdoc cref="Table{TItem}.OnDoubleClickRowCallback"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public Func<TItem, Task>? OnDoubleClickRowCallback { get; set; } |     public Func<TItem, Task>? OnDoubleClickRowCallback { get; set; } | ||||||
|  |     /// <inheritdoc cref="Table{TItem}.RowContentTemplate"/> | ||||||
|  |     [Parameter] | ||||||
|  |     public RenderFragment<TableRowContext<TItem>>? RowContentTemplate { get; set; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Table{TItem}.OnClickRowCallback"/> |     /// <inheritdoc cref="Table{TItem}.OnClickRowCallback"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public Func<TItem, Task>? OnClickRowCallback { get; set; } |     public Func<TItem, Task>? OnClickRowCallback { get; set; } | ||||||
| @@ -146,6 +167,9 @@ public partial class AdminTable<TItem> where TItem : class, new() | |||||||
|     [Parameter] |     [Parameter] | ||||||
|     public IDataService<TItem> DataService { get; set; } |     public IDataService<TItem> DataService { get; set; } | ||||||
|  |  | ||||||
|  |     [Parameter] | ||||||
|  |     public string? Id { get; set; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Table{TItem}.CreateItemCallback"/> |     /// <inheritdoc cref="Table{TItem}.CreateItemCallback"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public Func<TItem> CreateItemCallback { get; set; } |     public Func<TItem> CreateItemCallback { get; set; } | ||||||
|   | |||||||
| @@ -72,7 +72,7 @@ public partial class HardwareInfoPage : IDisposable | |||||||
|             ChartDataSource.Options.Title = Localizer[nameof(HistoryHardwareInfo)]; |             ChartDataSource.Options.Title = Localizer[nameof(HistoryHardwareInfo)]; | ||||||
|             ChartDataSource.Options.X.Title = Localizer["DateTime"]; |             ChartDataSource.Options.X.Title = Localizer["DateTime"]; | ||||||
|             ChartDataSource.Options.Y.Title = Localizer["Data"]; |             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() |             ChartDataSource.Data.Add(new ChartDataset() | ||||||
|             { |             { | ||||||
|                 Tension = 0.4f, |                 Tension = 0.4f, | ||||||
| @@ -116,7 +116,7 @@ public partial class HardwareInfoPage : IDisposable | |||||||
|         else |         else | ||||||
|         { |         { | ||||||
|             var hisHardwareInfos = await HardwareJob.GetHistoryHardwareInfos(); |             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[0].Data = hisHardwareInfos.Select(a => (object)a.CpuUsage); | ||||||
|             ChartDataSource.Data[1].Data = hisHardwareInfos.Select(a => (object)a.MemoryUsage); |             ChartDataSource.Data[1].Data = hisHardwareInfos.Select(a => (object)a.MemoryUsage); | ||||||
|             ChartDataSource.Data[2].Data = hisHardwareInfos.Select(a => (object)a.DriveUsage); |             ChartDataSource.Data[2].Data = hisHardwareInfos.Select(a => (object)a.DriveUsage); | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
|  |  | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" /> | 		<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" /> | ||||||
| 		<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.1" /> | 		<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.4" /> | ||||||
| 		<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" /> | 		<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" /> | ||||||
| 		<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" /> | 		<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" /> | ||||||
| 		<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.3" /> | 		<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.3" /> | ||||||
|   | |||||||
| @@ -92,7 +92,8 @@ public class Startup : AppStartup | |||||||
|              options.RootComponents.MaxJSRootComponents = 500; |              options.RootComponents.MaxJSRootComponents = 500; | ||||||
|              options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); |              options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); | ||||||
|              options.MaxBufferedUnacknowledgedRenderBatches = 20; |              options.MaxBufferedUnacknowledgedRenderBatches = 20; | ||||||
|              options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); |              options.DisconnectedCircuitMaxRetained = 1; | ||||||
|  |              options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10); | ||||||
|          }) |          }) | ||||||
|          .AddHubOptions(options => |          .AddHubOptions(options => | ||||||
|          { |          { | ||||||
| @@ -103,6 +104,7 @@ public class Startup : AppStartup | |||||||
|              options.ClientTimeoutInterval = TimeSpan.FromMinutes(2); |              options.ClientTimeoutInterval = TimeSpan.FromMinutes(2); | ||||||
|              options.KeepAliveInterval = TimeSpan.FromSeconds(15); |              options.KeepAliveInterval = TimeSpan.FromSeconds(15); | ||||||
|              options.HandshakeTimeout = TimeSpan.FromSeconds(30); |              options.HandshakeTimeout = TimeSpan.FromSeconds(30); | ||||||
|  |  | ||||||
|          }); |          }); | ||||||
|  |  | ||||||
| #else | #else | ||||||
| @@ -112,7 +114,8 @@ public class Startup : AppStartup | |||||||
|             options.RootComponents.MaxJSRootComponents = 500; |             options.RootComponents.MaxJSRootComponents = 500; | ||||||
|             options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); |             options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); | ||||||
|             options.MaxBufferedUnacknowledgedRenderBatches = 20; |             options.MaxBufferedUnacknowledgedRenderBatches = 20; | ||||||
|             options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); |              options.DisconnectedCircuitMaxRetained = 1; | ||||||
|  |              options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10); | ||||||
|         }).AddHubOptions(options => |         }).AddHubOptions(options => | ||||||
|         { |         { | ||||||
|             //单个传入集线器消息的最大大小。默认 32 KB |             //单个传入集线器消息的最大大小。默认 32 KB | ||||||
|   | |||||||
| @@ -15,15 +15,11 @@ | |||||||
| 		<PublishReadyToRunComposite>true</PublishReadyToRunComposite> | 		<PublishReadyToRunComposite>true</PublishReadyToRunComposite> | ||||||
| 		<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon> | 		<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon> | ||||||
|  |  | ||||||
|  | 		<CETCompat>false</CETCompat> | ||||||
|  | 		<ServerGarbageCollection>true</ServerGarbageCollection> | ||||||
| 		<!--动态适用GC--> | 		<!--动态适用GC--> | ||||||
| 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | 		<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> | ||||||
| 		<CETCompat>false</CETCompat> | 		 | ||||||
| 		<!--使用自托管线程池--> |  | ||||||
| 		<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> --> |  | ||||||
|  |  | ||||||
| 		<!--使用工作站GC--> |  | ||||||
| 		<!--<ServerGarbageCollection>true</ServerGarbageCollection>--> |  | ||||||
|  |  | ||||||
| 		<!--<PlatformTarget>x86</PlatformTarget>--> | 		<!--<PlatformTarget>x86</PlatformTarget>--> | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
| @@ -477,7 +477,7 @@ public class ConcurrentList<T> : IList<T>, IReadOnlyList<T> | |||||||
|     { |     { | ||||||
|         lock (((ICollection)m_list).SyncRoot) |         lock (((ICollection)m_list).SyncRoot) | ||||||
|         { |         { | ||||||
|             return m_list.IndexOf(item); |             return m_list.LastIndexOf(item); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -82,7 +82,7 @@ public static class ObjectExtensions | |||||||
|     /// <typeparam name="T"></typeparam> |     /// <typeparam name="T"></typeparam> | ||||||
|     /// <param name="dic">字典</param> |     /// <param name="dic">字典</param> | ||||||
|     /// <param name="newDic">新字典</param> |     /// <param name="newDic">新字典</param> | ||||||
|     internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic) |     internal static void AddOrUpdate<T>(this NonBlockingDictionary<string, T> dic, Dictionary<string, T> newDic) | ||||||
|     { |     { | ||||||
|         foreach (var (key, value) in newDic) |         foreach (var (key, value) in newDic) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ internal class CacheManager | |||||||
| { | { | ||||||
|     private IMemoryCache Cache { get; set; } |     private IMemoryCache Cache { get; set; } | ||||||
|  |  | ||||||
|     private IServiceProvider Provider { get; set; } |     private static IServiceProvider Provider => App.RootServices; | ||||||
|  |  | ||||||
|     [NotNull] |     [NotNull] | ||||||
|     private static CacheManager? Instance { get; set; } |     private static CacheManager? Instance { get; set; } | ||||||
| @@ -40,8 +40,7 @@ internal class CacheManager | |||||||
|     static CacheManager() |     static CacheManager() | ||||||
|     { |     { | ||||||
|         Instance = new(); |         Instance = new(); | ||||||
|         Instance.Provider = App.RootServices; |         Instance.Cache = Provider.GetRequiredService<IMemoryCache>(); | ||||||
|         Instance.Cache = Instance.Provider.GetRequiredService<IMemoryCache>(); |  | ||||||
|         Options = App.RootServices.GetRequiredService<IOptions<BootstrapBlazorOptions>>().Value; |         Options = App.RootServices.GetRequiredService<IOptions<BootstrapBlazorOptions>>().Value; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -236,7 +235,7 @@ internal class CacheManager | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static IStringLocalizer? CreateLocalizerByType(Type resourceSource) => resourceSource.Assembly.IsDynamic |     public static IStringLocalizer? CreateLocalizerByType(Type resourceSource) => resourceSource.Assembly.IsDynamic | ||||||
|         ? null |         ? null | ||||||
|         : Instance.Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource); |         : Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获得 <see cref="JsonLocalizationOptions"/> 值 |     /// 获得 <see cref="JsonLocalizationOptions"/> 值 | ||||||
| @@ -244,7 +243,7 @@ internal class CacheManager | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     private static JsonLocalizationOptions GetJsonLocalizationOption() |     private static JsonLocalizationOptions GetJsonLocalizationOption() | ||||||
|     { |     { | ||||||
|         var localizationOptions = Instance.Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>(); |         var localizationOptions = Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>(); | ||||||
|         return localizationOptions.Value; |         return localizationOptions.Value; | ||||||
|     } |     } | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -253,7 +252,7 @@ internal class CacheManager | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     private static BootstrapBlazorOptions GetBootstrapBlazorOption() |     private static BootstrapBlazorOptions GetBootstrapBlazorOption() | ||||||
|     { |     { | ||||||
|         var localizationOptions = Instance.Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>(); |         var localizationOptions = Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>(); | ||||||
|         return localizationOptions.Value; |         return localizationOptions.Value; | ||||||
|     } |     } | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -269,7 +268,7 @@ internal class CacheManager | |||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|         IStringLocalizer? ret = null; |         IStringLocalizer? ret = null; | ||||||
|         var factories = Instance.Provider.GetServices<IStringLocalizerFactory>(); |         var factories = Provider.GetServices<IStringLocalizerFactory>(); | ||||||
|         var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory); |         var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory); | ||||||
|         if (factory != null) |         if (factory != null) | ||||||
|         { |         { | ||||||
| @@ -345,7 +344,7 @@ internal class CacheManager | |||||||
|     /// <param name="typeName"></param> |     /// <param name="typeName"></param> | ||||||
|     /// <param name="includeParentCultures"></param> |     /// <param name="includeParentCultures"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Instance.Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures); |     public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures); | ||||||
|     #endregion |     #endregion | ||||||
|  |  | ||||||
|     #region DisplayName |     #region DisplayName | ||||||
|   | |||||||
| @@ -66,7 +66,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | |||||||
|                 } |                 } | ||||||
|                 catch (Exception ex) |                 catch (Exception ex) | ||||||
|                 { |                 { | ||||||
|                     Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); |                     if (Logger?.IsEnabled(LogLevel.Error) == true) | ||||||
|  |                         Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); | ||||||
|                 } |                 } | ||||||
|                 return ret; |                 return ret; | ||||||
|             } |             } | ||||||
| @@ -176,7 +177,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba | |||||||
|         localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name); |         localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name); | ||||||
|         if (jsonLocalizationOptions.IgnoreLocalizerMissing == false) |         if (jsonLocalizationOptions.IgnoreLocalizerMissing == false) | ||||||
|         { |         { | ||||||
|             Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); |             if (Logger?.IsEnabled(LogLevel.Information) == true) | ||||||
|  |                 Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name); | ||||||
|         } |         } | ||||||
|         _missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}"); |         _missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}"); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ | |||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" /> | 		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" /> | ||||||
| 		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> | 		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> | ||||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.11.1" /> | 		<PackageReference Include="BootstrapBlazor" Version="9.11.4" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|  |  | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
|   | |||||||
| @@ -205,7 +205,7 @@ public static class ObjectExtensions | |||||||
|     /// <typeparam name="T"></typeparam> |     /// <typeparam name="T"></typeparam> | ||||||
|     /// <param name="dic">字典</param> |     /// <param name="dic">字典</param> | ||||||
|     /// <param name="newDic">新字典</param> |     /// <param name="newDic">新字典</param> | ||||||
|     internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic) |     internal static void AddOrUpdate<T>(this NonBlockingDictionary<string, T> dic, Dictionary<string, T> newDic) | ||||||
|     { |     { | ||||||
|         foreach (var (key, value) in newDic) |         foreach (var (key, value) in newDic) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -94,7 +94,7 @@ public static class AspNetCoreBuilderServiceCollectionExtensions | |||||||
|     /// <param name="mvcBuilder"></param> |     /// <param name="mvcBuilder"></param> | ||||||
|     /// <param name="configure"></param> |     /// <param name="configure"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action<ConcurrentDictionary<Type, Type>> configure = default) |     public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action<NonBlockingDictionary<Type, Type>> configure = default) | ||||||
|     { |     { | ||||||
|         mvcBuilder.Services.AddFromConvertBinding(configure); |         mvcBuilder.Services.AddFromConvertBinding(configure); | ||||||
|  |  | ||||||
| @@ -107,13 +107,13 @@ public static class AspNetCoreBuilderServiceCollectionExtensions | |||||||
|     /// <param name="services"></param> |     /// <param name="services"></param> | ||||||
|     /// <param name="configure"></param> |     /// <param name="configure"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action<ConcurrentDictionary<Type, Type>> configure = default) |     public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action<NonBlockingDictionary<Type, Type>> configure = default) | ||||||
|     { |     { | ||||||
|         // 非 Web 环境跳过注册 |         // 非 Web 环境跳过注册 | ||||||
|         if (App.WebHostEnvironment == default) return services; |         if (App.WebHostEnvironment == default) return services; | ||||||
|  |  | ||||||
|         // 定义模型绑定转换器集合 |         // 定义模型绑定转换器集合 | ||||||
|         var modelBinderConverts = new ConcurrentDictionary<Type, Type>(); |         var modelBinderConverts = new NonBlockingDictionary<Type, Type>(); | ||||||
|         modelBinderConverts.TryAdd(typeof(DateTime), typeof(DateTimeModelConvertBinder)); |         modelBinderConverts.TryAdd(typeof(DateTime), typeof(DateTimeModelConvertBinder)); | ||||||
|         modelBinderConverts.TryAdd(typeof(DateTimeOffset), typeof(DateTimeOffsetModelConvertBinder)); |         modelBinderConverts.TryAdd(typeof(DateTimeOffset), typeof(DateTimeOffsetModelConvertBinder)); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -27,13 +27,13 @@ public class FromConvertBinder : IModelBinder | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 定义模型绑定转换器集合 |     /// 定义模型绑定转换器集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts; |     private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 构造函数 |     /// 构造函数 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="modelBinderConverts">定义模型绑定转换器集合</param> |     /// <param name="modelBinderConverts">定义模型绑定转换器集合</param> | ||||||
|     public FromConvertBinder(ConcurrentDictionary<Type, Type> modelBinderConverts) |     public FromConvertBinder(NonBlockingDictionary<Type, Type> modelBinderConverts) | ||||||
|     { |     { | ||||||
|         _modelBinderConverts = modelBinderConverts; |         _modelBinderConverts = modelBinderConverts; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -28,13 +28,13 @@ public class FromConvertBinderProvider : IModelBinderProvider | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 定义模型绑定转换器集合 |     /// 定义模型绑定转换器集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts; |     private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 构造函数 |     /// 构造函数 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="modelBinderConverts">定义模型绑定转换器集合</param> |     /// <param name="modelBinderConverts">定义模型绑定转换器集合</param> | ||||||
|     public FromConvertBinderProvider(ConcurrentDictionary<Type, Type> modelBinderConverts) |     public FromConvertBinderProvider(NonBlockingDictionary<Type, Type> modelBinderConverts) | ||||||
|     { |     { | ||||||
|         _modelBinderConverts = modelBinderConverts; |         _modelBinderConverts = modelBinderConverts; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ public static class DataValidator | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 验证类型正则表达式 |     /// 验证类型正则表达式 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static readonly ConcurrentDictionary<string, ValidationItemMetadataAttribute> ValidationItemMetadatas; |     private static readonly NonBlockingDictionary<string, ValidationItemMetadataAttribute> ValidationItemMetadatas; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 构造函数 |     /// 构造函数 | ||||||
| @@ -57,7 +57,7 @@ public static class DataValidator | |||||||
|         ValidationItemMetadatas = GetValidationValidationItemMetadatas(); |         ValidationItemMetadatas = GetValidationValidationItemMetadatas(); | ||||||
|  |  | ||||||
|         // 缓存所有正则表达式 |         // 缓存所有正则表达式 | ||||||
|         GetValidationTypeValidationItemMetadataCached = new ConcurrentDictionary<object, (string, ValidationItemMetadataAttribute)>(); |         GetValidationTypeValidationItemMetadataCached = new NonBlockingDictionary<object, (string, ValidationItemMetadataAttribute)>(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -203,7 +203,7 @@ public static class DataValidator | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取验证类型验证Item集合 |     /// 获取验证类型验证Item集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static readonly ConcurrentDictionary<object, (string, ValidationItemMetadataAttribute)> GetValidationTypeValidationItemMetadataCached; |     private static readonly NonBlockingDictionary<object, (string, ValidationItemMetadataAttribute)> GetValidationTypeValidationItemMetadataCached; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取验证类型正则表达式(需要缓存) |     /// 获取验证类型正则表达式(需要缓存) | ||||||
| @@ -267,9 +267,9 @@ public static class DataValidator | |||||||
|     /// 获取验证类型所有有效的正则表达式 |     /// 获取验证类型所有有效的正则表达式 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     private static ConcurrentDictionary<string, ValidationItemMetadataAttribute> GetValidationValidationItemMetadatas() |     private static NonBlockingDictionary<string, ValidationItemMetadataAttribute> GetValidationValidationItemMetadatas() | ||||||
|     { |     { | ||||||
|         var vaidationItems = new ConcurrentDictionary<string, ValidationItemMetadataAttribute>(); |         var vaidationItems = new NonBlockingDictionary<string, ValidationItemMetadataAttribute>(); | ||||||
|  |  | ||||||
|         // 查找所有 [ValidationMessageType] 类型中的 [ValidationMessage] 消息定义 |         // 查找所有 [ValidationMessageType] 类型中的 [ValidationMessage] 消息定义 | ||||||
|         var customErrorMessages = ValidationMessageTypes.SelectMany(u => u.GetFields() |         var customErrorMessages = ValidationMessageTypes.SelectMany(u => u.GetFields() | ||||||
|   | |||||||
| @@ -353,7 +353,7 @@ public static class DependencyInjectionServiceCollectionExtensions | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 类型名称集合 |     /// 类型名称集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static readonly ConcurrentDictionary<string, Type> TypeNamedCollection; |     private static readonly NonBlockingDictionary<string, Type> TypeNamedCollection; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 创建代理方法 |     /// 创建代理方法 | ||||||
| @@ -374,7 +374,7 @@ public static class DependencyInjectionServiceCollectionExtensions | |||||||
|         GlobalServiceProxyType = App.EffectiveTypes |         GlobalServiceProxyType = App.EffectiveTypes | ||||||
|             .FirstOrDefault(u => typeof(AspectDispatchProxy).IsAssignableFrom(u) && typeof(IGlobalDispatchProxy).IsAssignableFrom(u) && u.IsClass && !u.IsInterface && !u.IsAbstract); |             .FirstOrDefault(u => typeof(AspectDispatchProxy).IsAssignableFrom(u) && typeof(IGlobalDispatchProxy).IsAssignableFrom(u) && u.IsClass && !u.IsInterface && !u.IsAbstract); | ||||||
|  |  | ||||||
|         TypeNamedCollection = new ConcurrentDictionary<string, Type>(); |         TypeNamedCollection = new NonBlockingDictionary<string, Type>(); | ||||||
|         DispatchCreateMethod = typeof(AspectDispatchProxy).GetMethod(nameof(AspectDispatchProxy.Create)); |         DispatchCreateMethod = typeof(AspectDispatchProxy).GetMethod(nameof(AspectDispatchProxy.Create)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -28,21 +28,21 @@ internal static class Penetrates | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 请求动词映射字典 |     /// 请求动词映射字典 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal static ConcurrentDictionary<string, string> VerbToHttpMethods { get; private set; } |     internal static NonBlockingDictionary<string, string> VerbToHttpMethods { get; private set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 控制器排序集合 |     /// 控制器排序集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal static ConcurrentDictionary<string, (string, int, Type)> ControllerOrderCollection { get; set; } |     internal static NonBlockingDictionary<string, (string, int, Type)> ControllerOrderCollection { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 构造函数 |     /// 构造函数 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     static Penetrates() |     static Penetrates() | ||||||
|     { |     { | ||||||
|         ControllerOrderCollection = new ConcurrentDictionary<string, (string, int, Type)>(); |         ControllerOrderCollection = new NonBlockingDictionary<string, (string, int, Type)>(); | ||||||
|  |  | ||||||
|         VerbToHttpMethods = new ConcurrentDictionary<string, string> |         VerbToHttpMethods = new NonBlockingDictionary<string, string> | ||||||
|         { |         { | ||||||
|             ["post"] = "POST", |             ["post"] = "POST", | ||||||
|             ["add"] = "POST", |             ["add"] = "POST", | ||||||
| @@ -67,13 +67,13 @@ internal static class Penetrates | |||||||
|             ["patch"] = "PATCH" |             ["patch"] = "PATCH" | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         //IsApiControllerCached = new ConcurrentDictionary<Type, bool>(); |         //IsApiControllerCached = new NonBlockingDictionary<Type, bool>(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     ///// <summary> |     ///// <summary> | ||||||
|     ///// <see cref="IsApiController(Type)"/> 缓存集合 |     ///// <see cref="IsApiController(Type)"/> 缓存集合 | ||||||
|     ///// </summary> |     ///// </summary> | ||||||
|     //private static readonly ConcurrentDictionary<Type, bool> IsApiControllerCached; |     //private static readonly NonBlockingDictionary<Type, bool> IsApiControllerCached; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 是否是Api控制器 |     /// 是否是Api控制器 | ||||||
|   | |||||||
| @@ -61,7 +61,7 @@ internal sealed class EventBusHostedService : BackgroundService | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 事件处理程序集合 |     /// 事件处理程序集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private readonly ConcurrentDictionary<EventHandlerWrapper, EventHandlerWrapper> _eventHandlers = new(); |     private readonly NonBlockingDictionary<EventHandlerWrapper, EventHandlerWrapper> _eventHandlers = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 构造函数 |     /// 构造函数 | ||||||
| @@ -295,7 +295,8 @@ internal sealed class EventBusHostedService : BackgroundService | |||||||
|                         , retryAction: (total, times) => |                         , retryAction: (total, times) => | ||||||
|                         { |                         { | ||||||
|                             // 输出重试日志 |                             // 输出重试日志 | ||||||
|                             _logger.LogWarning("Retrying {times}/{total} times for {EventId}", times, total, eventSource.EventId); |                             if (_logger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning) == true) | ||||||
|  |                                 _logger.LogWarning("Retrying {times}/{total} times for {EventId}", times, total, eventSource.EventId); | ||||||
|                         }).ConfigureAwait(false); |                         }).ConfigureAwait(false); | ||||||
|                     } |                     } | ||||||
|                     else |                     else | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ public static class Oops | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 方法错误异常特性 |     /// 方法错误异常特性 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static readonly ConcurrentDictionary<MethodBase, MethodIfException> _errorMethods; |     private static readonly NonBlockingDictionary<MethodBase, MethodIfException> _errorMethods; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 错误代码类型 |     /// 错误代码类型 | ||||||
| @@ -41,7 +41,7 @@ public static class Oops | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 错误消息字典 |     /// 错误消息字典 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static readonly ConcurrentDictionary<string, string> _errorCodeMessages; |     private static readonly NonBlockingDictionary<string, string> _errorCodeMessages; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 友好异常设置 |     /// 友好异常设置 | ||||||
| @@ -53,7 +53,7 @@ public static class Oops | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     static Oops() |     static Oops() | ||||||
|     { |     { | ||||||
|         _errorMethods = new ConcurrentDictionary<MethodBase, MethodIfException>(); |         _errorMethods = new NonBlockingDictionary<MethodBase, MethodIfException>(); | ||||||
|         _friendlyExceptionSettings = App.GetConfig<FriendlyExceptionSettingsOptions>("FriendlyExceptionSettings", true); |         _friendlyExceptionSettings = App.GetConfig<FriendlyExceptionSettingsOptions>("FriendlyExceptionSettings", true); | ||||||
|         _errorCodeTypes = GetErrorCodeTypes(); |         _errorCodeTypes = GetErrorCodeTypes(); | ||||||
|         _errorCodeMessages = GetErrorCodeMessages(); |         _errorCodeMessages = GetErrorCodeMessages(); | ||||||
| @@ -258,9 +258,9 @@ public static class Oops | |||||||
|     /// 获取所有错误消息 |     /// 获取所有错误消息 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     private static ConcurrentDictionary<string, string> GetErrorCodeMessages() |     private static NonBlockingDictionary<string, string> GetErrorCodeMessages() | ||||||
|     { |     { | ||||||
|         var defaultErrorCodeMessages = new ConcurrentDictionary<string, string>(); |         var defaultErrorCodeMessages = new NonBlockingDictionary<string, string>(); | ||||||
|  |  | ||||||
|         // 查找所有 [ErrorCodeType] 类型中的 [ErrorCodeMetadata] 元数据定义 |         // 查找所有 [ErrorCodeType] 类型中的 [ErrorCodeMetadata] 元数据定义 | ||||||
|         var errorCodeMessages = _errorCodeTypes.SelectMany(u => u.GetFields().Where(u => u.IsDefined(typeof(ErrorCodeItemMetadataAttribute)))) |         var errorCodeMessages = _errorCodeTypes.SelectMany(u => u.GetFields().Where(u => u.IsDefined(typeof(ErrorCodeItemMetadataAttribute)))) | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ public static class ILoggerExtensions | |||||||
|     /// 设置日志上下文 |     /// 设置日志上下文 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="logger"></param> |     /// <param name="logger"></param> | ||||||
|     /// <param name="properties">建议使用 ConcurrentDictionary 类型</param> |     /// <param name="properties">建议使用 NonBlockingDictionary 类型</param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static IDisposable ScopeContext(this ILogger logger, IDictionary<string, object> properties) |     public static IDisposable ScopeContext(this ILogger logger, IDictionary<string, object> properties) | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -82,7 +82,7 @@ public static class StringLoggingExtensions | |||||||
|     /// 配置日志上下文 |     /// 配置日志上下文 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="message"></param> |     /// <param name="message"></param> | ||||||
|     /// <param name="properties">建议使用 ConcurrentDictionary 类型</param> |     /// <param name="properties">建议使用 NonBlockingDictionary 类型</param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static StringLoggingPart ScopeContext(this string message, IDictionary<string, object> properties) |     public static StringLoggingPart ScopeContext(this string message, IDictionary<string, object> properties) | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ public sealed class FileLoggerProvider : ILoggerProvider, ISupportExternalScope | |||||||
|     /// 记录日志所有滚动文件名 |     /// 记录日志所有滚动文件名 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <remarks>只有 MaxRollingFiles 和 FileSizeLimitBytes 大于 0 有效</remarks> |     /// <remarks>只有 MaxRollingFiles 和 FileSizeLimitBytes 大于 0 有效</remarks> | ||||||
|     internal readonly ConcurrentDictionary<string, FileInfo> _rollingFileNames = new(); |     internal readonly NonBlockingDictionary<string, FileInfo> _rollingFileNames = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 文件日志写入器 |     /// 文件日志写入器 | ||||||
|   | |||||||
| @@ -94,7 +94,7 @@ public sealed partial class StringLoggingPart | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 配置日志上下文 |     /// 配置日志上下文 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="properties">建议使用 ConcurrentDictionary 类型</param> |     /// <param name="properties">建议使用 NonBlockingDictionary 类型</param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public StringLoggingPart ScopeContext(IDictionary<string, object> properties) |     public StringLoggingPart ScopeContext(IDictionary<string, object> properties) | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -57,7 +57,7 @@ public static class Log | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 配置日志上下文 |     /// 配置日志上下文 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="properties">建议使用 ConcurrentDictionary 类型</param> |     /// <param name="properties">建议使用 NonBlockingDictionary 类型</param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static (ILogger logger, IDisposable scope) ScopeContext(IDictionary<string, object> properties) |     public static (ILogger logger, IDisposable scope) ScopeContext(IDictionary<string, object> properties) | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ internal sealed class JobCancellationToken : IJobCancellationToken | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 取消作业执行 Token 集合 |     /// 取消作业执行 Token 集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private readonly ConcurrentDictionary<string, CancellationTokenSource> _cancellationTokenSources; |     private readonly NonBlockingDictionary<string, CancellationTokenSource> _cancellationTokenSources; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 作业调度器日志服务 |     /// 作业调度器日志服务 | ||||||
|   | |||||||
| @@ -167,7 +167,7 @@ public partial class JobDetail | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 带命名规则的数据库列名 |     /// 带命名规则的数据库列名 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private readonly ConcurrentDictionary<NamingConventions, string[]> _namingColumnNames = new(); |     private readonly NonBlockingDictionary<NamingConventions, string[]> _namingColumnNames = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取数据库列名 |     /// 获取数据库列名 | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 作业计划集合 |     /// 作业计划集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private readonly ConcurrentDictionary<string, Scheduler> _schedulers = new(); |     private readonly NonBlockingDictionary<string, Scheduler> _schedulers = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 作业计划构建器集合 |     /// 作业计划构建器集合 | ||||||
|   | |||||||
| @@ -369,11 +369,13 @@ internal sealed class ScheduleHostedService : BackgroundService | |||||||
|                             // 写入作业执行详细日志 |                             // 写入作业执行详细日志 | ||||||
|                             if (executionException == null) |                             if (executionException == null) | ||||||
|                             { |                             { | ||||||
|                                 jobLogger?.LogInformation("{jobExecutingContext}", jobExecutingContext); |                                 if (jobLogger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information) == true) | ||||||
|  |                                     jobLogger?.LogInformation("{jobExecutingContext}", jobExecutingContext); | ||||||
|                             } |                             } | ||||||
|                             else |                             else | ||||||
|                             { |                             { | ||||||
|                                 jobLogger?.LogError(executionException, "{jobExecutingContext}", jobExecutingContext); |                                 if (jobLogger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Error) == true) | ||||||
|  |                                     jobLogger?.LogError(executionException, "{jobExecutingContext}", jobExecutingContext); | ||||||
|                             } |                             } | ||||||
|  |  | ||||||
|                             // 记录作业触发器运行信息 |                             // 记录作业触发器运行信息 | ||||||
|   | |||||||
| @@ -380,7 +380,7 @@ public partial class Trigger | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 带命名规则的数据库列名 |     /// 带命名规则的数据库列名 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private readonly ConcurrentDictionary<NamingConventions, string[]> _namingColumnNames = new(); |     private readonly NonBlockingDictionary<NamingConventions, string[]> _namingColumnNames = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取数据库列名 |     /// 获取数据库列名 | ||||||
|   | |||||||
| @@ -83,11 +83,11 @@ public static class SpecificationDocumentBuilder | |||||||
|  |  | ||||||
|         // 初始化常量 |         // 初始化常量 | ||||||
|         _groupOrderRegex = new Regex(@"@(?<order>[0-9]+$)"); |         _groupOrderRegex = new Regex(@"@(?<order>[0-9]+$)"); | ||||||
|         GetActionGroupsCached = new ConcurrentDictionary<MethodInfo, IEnumerable<GroupExtraInfo>>(); |         GetActionGroupsCached = new NonBlockingDictionary<MethodInfo, IEnumerable<GroupExtraInfo>>(); | ||||||
|         GetControllerGroupsCached = new ConcurrentDictionary<Type, IEnumerable<GroupExtraInfo>>(); |         GetControllerGroupsCached = new NonBlockingDictionary<Type, IEnumerable<GroupExtraInfo>>(); | ||||||
|         GetGroupOpenApiInfoCached = new ConcurrentDictionary<string, SpecificationOpenApiInfo>(); |         GetGroupOpenApiInfoCached = new NonBlockingDictionary<string, SpecificationOpenApiInfo>(); | ||||||
|         GetControllerTagCached = new ConcurrentDictionary<ControllerActionDescriptor, string>(); |         GetControllerTagCached = new NonBlockingDictionary<ControllerActionDescriptor, string>(); | ||||||
|         GetActionTagCached = new ConcurrentDictionary<ApiDescription, string>(); |         GetActionTagCached = new NonBlockingDictionary<ApiDescription, string>(); | ||||||
|  |  | ||||||
|         // 默认分组,支持多个逗号分割 |         // 默认分组,支持多个逗号分割 | ||||||
|         DocumentGroupExtras = new List<GroupExtraInfo> { ResolveGroupExtraInfo(_specificationDocumentSettings.DefaultGroupName) }; |         DocumentGroupExtras = new List<GroupExtraInfo> { ResolveGroupExtraInfo(_specificationDocumentSettings.DefaultGroupName) }; | ||||||
| @@ -143,7 +143,7 @@ public static class SpecificationDocumentBuilder | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取分组信息缓存集合 |     /// 获取分组信息缓存集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static readonly ConcurrentDictionary<string, SpecificationOpenApiInfo> GetGroupOpenApiInfoCached; |     private static readonly NonBlockingDictionary<string, SpecificationOpenApiInfo> GetGroupOpenApiInfoCached; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取分组配置信息 |     /// 获取分组配置信息 | ||||||
| @@ -738,7 +738,7 @@ public static class SpecificationDocumentBuilder | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取控制器组缓存集合 |     /// 获取控制器组缓存集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static readonly ConcurrentDictionary<Type, IEnumerable<GroupExtraInfo>> GetControllerGroupsCached; |     private static readonly NonBlockingDictionary<Type, IEnumerable<GroupExtraInfo>> GetControllerGroupsCached; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取控制器分组列表 |     /// 获取控制器分组列表 | ||||||
| @@ -773,7 +773,7 @@ public static class SpecificationDocumentBuilder | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// <see cref="GetActionGroups(MethodInfo)"/> 缓存集合 |     /// <see cref="GetActionGroups(MethodInfo)"/> 缓存集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static readonly ConcurrentDictionary<MethodInfo, IEnumerable<GroupExtraInfo>> GetActionGroupsCached; |     private static readonly NonBlockingDictionary<MethodInfo, IEnumerable<GroupExtraInfo>> GetActionGroupsCached; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取动作方法分组列表 |     /// 获取动作方法分组列表 | ||||||
| @@ -808,7 +808,7 @@ public static class SpecificationDocumentBuilder | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// <see cref="GetActionTag(ApiDescription)"/> 缓存集合 |     /// <see cref="GetActionTag(ApiDescription)"/> 缓存集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static readonly ConcurrentDictionary<ControllerActionDescriptor, string> GetControllerTagCached; |     private static readonly NonBlockingDictionary<ControllerActionDescriptor, string> GetControllerTagCached; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取控制器标签 |     /// 获取控制器标签 | ||||||
| @@ -835,7 +835,7 @@ public static class SpecificationDocumentBuilder | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// <see cref="GetActionTag(ApiDescription)"/> 缓存集合 |     /// <see cref="GetActionTag(ApiDescription)"/> 缓存集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static readonly ConcurrentDictionary<ApiDescription, string> GetActionTagCached; |     private static readonly NonBlockingDictionary<ApiDescription, string> GetActionTagCached; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取动作方法标签 |     /// 获取动作方法标签 | ||||||
|   | |||||||
| @@ -51,12 +51,12 @@ public static class UnifyContext | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 规范化结果提供器 |     /// 规范化结果提供器 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal static ConcurrentDictionary<string, UnifyMetadata> UnifyProviders = new(); |     internal static NonBlockingDictionary<string, UnifyMetadata> UnifyProviders = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 规范化序列化配置 |     /// 规范化序列化配置 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal static ConcurrentDictionary<string, object> UnifySerializerSettings = new(); |     internal static NonBlockingDictionary<string, object> UnifySerializerSettings = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取异常元数据 |     /// 获取异常元数据 | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ using System.Collections.Concurrent; | |||||||
| namespace ThingsGateway.Extension; | namespace ThingsGateway.Extension; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| ///     <see cref="ConcurrentDictionary{TKey, TValue}" /> 拓展类 | ///     <see cref="NonBlockingDictionary{TKey, TValue}" /> 拓展类 | ||||||
| /// </summary> | /// </summary> | ||||||
| internal static class ConcurrentDictionaryExtensions | internal static class ConcurrentDictionaryExtensions | ||||||
| { | { | ||||||
| @@ -24,7 +24,7 @@ internal static class ConcurrentDictionaryExtensions | |||||||
|     /// <typeparam name="TKey">字典键类型</typeparam> |     /// <typeparam name="TKey">字典键类型</typeparam> | ||||||
|     /// <typeparam name="TValue">字典值类型</typeparam> |     /// <typeparam name="TValue">字典值类型</typeparam> | ||||||
|     /// <param name="dictionary"> |     /// <param name="dictionary"> | ||||||
|     ///     <see cref="ConcurrentDictionary{TKey, TValue}" /> |     ///     <see cref="NonBlockingDictionary{TKey, TValue}" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="key"> |     /// <param name="key"> | ||||||
|     ///     <typeparamref name="TKey" /> |     ///     <typeparamref name="TKey" /> | ||||||
| @@ -36,7 +36,7 @@ internal static class ConcurrentDictionaryExtensions | |||||||
|     /// <returns> |     /// <returns> | ||||||
|     ///     <see cref="bool" /> |     ///     <see cref="bool" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     internal static bool TryUpdate<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary |     internal static bool TryUpdate<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dictionary | ||||||
|         , TKey key |         , TKey key | ||||||
|         , Func<TValue, TValue> updateFactory |         , Func<TValue, TValue> updateFactory | ||||||
|         , out TValue? value) |         , out TValue? value) | ||||||
|   | |||||||
| @@ -241,7 +241,7 @@ internal static class IDictionaryExtensions | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <remarks>其中键是由值通过给定的选择器函数生成的。</remarks> |     /// <remarks>其中键是由值通过给定的选择器函数生成的。</remarks> | ||||||
|     /// <param name="dictionary"> |     /// <param name="dictionary"> | ||||||
|     ///     <see cref="ConcurrentDictionary{TKey, TValue}" /> |     ///     <see cref="NonBlockingDictionary{TKey, TValue}" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="values"> |     /// <param name="values"> | ||||||
|     ///     <see cref="IEnumerable{T}" /> |     ///     <see cref="IEnumerable{T}" /> | ||||||
| @@ -249,7 +249,7 @@ internal static class IDictionaryExtensions | |||||||
|     /// <param name="keySelector">键选择器</param> |     /// <param name="keySelector">键选择器</param> | ||||||
|     /// <typeparam name="TKey">字典键类型</typeparam> |     /// <typeparam name="TKey">字典键类型</typeparam> | ||||||
|     /// <typeparam name="TValue">字典值类型</typeparam> |     /// <typeparam name="TValue">字典值类型</typeparam> | ||||||
|     internal static void TryAdd<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary, |     internal static void TryAdd<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dictionary, | ||||||
|         IEnumerable<TValue>? values, Func<TValue, TKey> keySelector) |         IEnumerable<TValue>? values, Func<TValue, TKey> keySelector) | ||||||
|         where TKey : notnull |         where TKey : notnull | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -21,20 +21,20 @@ internal sealed class CoreOptions | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     已注册的组件元数据集合 |     ///     已注册的组件元数据集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal readonly ConcurrentDictionary<string, ComponentMetadata> _metadataOfRegistered; |     internal readonly NonBlockingDictionary<string, ComponentMetadata> _metadataOfRegistered; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     子选项集合 |     ///     子选项集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal readonly ConcurrentDictionary<Type, object> _optionsInstances; |     internal readonly NonBlockingDictionary<Type, object> _optionsInstances; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     <inheritdoc cref="CoreOptions" /> |     ///     <inheritdoc cref="CoreOptions" /> | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal CoreOptions() |     internal CoreOptions() | ||||||
|     { |     { | ||||||
|         _optionsInstances = new ConcurrentDictionary<Type, object>(); |         _optionsInstances = new NonBlockingDictionary<Type, object>(); | ||||||
|         _metadataOfRegistered = new ConcurrentDictionary<string, ComponentMetadata>(StringComparer.OrdinalIgnoreCase); |         _metadataOfRegistered = new NonBlockingDictionary<string, ComponentMetadata>(StringComparer.OrdinalIgnoreCase); | ||||||
|  |  | ||||||
|         EntryComponentTypes = []; |         EntryComponentTypes = []; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ public sealed class ObjectPropertyGetter<T> where T : class | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     对象类型实例属性值访问器集合 |     ///     对象类型实例属性值访问器集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal readonly ConcurrentDictionary<string, Func<object, object?>> _propertyGetters = new(); |     internal readonly NonBlockingDictionary<string, Func<object, object?>> _propertyGetters = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     <inheritdoc cref="ObjectPropertyGetter{T}" /> |     ///     <inheritdoc cref="ObjectPropertyGetter{T}" /> | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ public sealed class ObjectPropertySetter<T> where T : class | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     对象类型实例属性值设置器集合 |     ///     对象类型实例属性值设置器集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal readonly ConcurrentDictionary<string, Action<object, object?>> _propertySetters = new(); |     internal readonly NonBlockingDictionary<string, Action<object, object?>> _propertySetters = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     <inheritdoc cref="ObjectPropertySetter{T}" /> |     ///     <inheritdoc cref="ObjectPropertySetter{T}" /> | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ public sealed class HttpDeclarativeBuilder | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     HTTP 声明式 <see cref="IHttpDeclarativeExtractor" /> 提取器集合 |     ///     HTTP 声明式 <see cref="IHttpDeclarativeExtractor" /> 提取器集合 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal static readonly ConcurrentDictionary<Type, IHttpDeclarativeExtractor> _extractors = new([ |     internal static readonly NonBlockingDictionary<Type, IHttpDeclarativeExtractor> _extractors = new([ | ||||||
|         new(typeof(BaseAddressDeclarativeExtractor), new BaseAddressDeclarativeExtractor()), |         new(typeof(BaseAddressDeclarativeExtractor), new BaseAddressDeclarativeExtractor()), | ||||||
|         new(typeof(ValidationDeclarativeExtractor), new ValidationDeclarativeExtractor()), |         new(typeof(ValidationDeclarativeExtractor), new ValidationDeclarativeExtractor()), | ||||||
|         new(typeof(AutoSetHostHeaderDeclarativeExtractor), new AutoSetHostHeaderDeclarativeExtractor()), |         new(typeof(AutoSetHostHeaderDeclarativeExtractor), new AutoSetHostHeaderDeclarativeExtractor()), | ||||||
| @@ -56,7 +56,7 @@ public sealed class HttpDeclarativeBuilder | |||||||
|     ///     HTTP 声明式 <see cref="IHttpDeclarativeExtractor" /> 提取器集合(冻结) |     ///     HTTP 声明式 <see cref="IHttpDeclarativeExtractor" /> 提取器集合(冻结) | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <remarks>该集合用于确保某些 HTTP 声明式提取器始终位于最后。</remarks> |     /// <remarks>该集合用于确保某些 HTTP 声明式提取器始终位于最后。</remarks> | ||||||
|     internal static readonly ConcurrentDictionary<Type, IFrozenHttpDeclarativeExtractor> _frozenExtractors = new([ |     internal static readonly NonBlockingDictionary<Type, IFrozenHttpDeclarativeExtractor> _frozenExtractors = new([ | ||||||
|         new(typeof(MultipartDeclarativeExtractor), new MultipartDeclarativeExtractor()), |         new(typeof(MultipartDeclarativeExtractor), new MultipartDeclarativeExtractor()), | ||||||
|         new(typeof(HttpMultipartFormDataBuilderDeclarativeExtractor), |         new(typeof(HttpMultipartFormDataBuilderDeclarativeExtractor), | ||||||
|             new HttpMultipartFormDataBuilderDeclarativeExtractor()), |             new HttpMultipartFormDataBuilderDeclarativeExtractor()), | ||||||
|   | |||||||
| @@ -251,7 +251,8 @@ public sealed class ProfilerDelegatingHandler(ILogger<Logging> logger, IOptions< | |||||||
|         // 检查是否配置(注册)了日志程序 |         // 检查是否配置(注册)了日志程序 | ||||||
|         if (remoteOptions.IsLoggingRegistered) |         if (remoteOptions.IsLoggingRegistered) | ||||||
|         { |         { | ||||||
|             logger.Log(remoteOptions.ProfilerLogLevel, "{message}", message); |             if (logger?.IsEnabled(remoteOptions.ProfilerLogLevel) == true) | ||||||
|  |                 logger.Log(remoteOptions.ProfilerLogLevel, "{message}", message); | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ public class MessagePackContentProcessor : HttpContentProcessorBase | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     MessagePack 序列化器委托字典缓存 |     ///     MessagePack 序列化器委托字典缓存 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal static readonly ConcurrentDictionary<Type, Func<object, byte[]>> _serializerCache = new(); |     internal static readonly NonBlockingDictionary<Type, Func<object, byte[]>> _serializerCache = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     初始化 MessagePack 序列化器委托 |     ///     初始化 MessagePack 序列化器委托 | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ public class MemoryCache : Cache | |||||||
| { | { | ||||||
|     #region 属性 |     #region 属性 | ||||||
|     /// <summary>缓存核心</summary> |     /// <summary>缓存核心</summary> | ||||||
|     protected ConcurrentDictionary<String, CacheItem> _cache = new(); |     protected NonBlockingDictionary<String, CacheItem> _cache = new(); | ||||||
|  |  | ||||||
|     /// <summary>容量。容量超标时,采用LRU机制删除,默认100_000</summary> |     /// <summary>容量。容量超标时,采用LRU机制删除,默认100_000</summary> | ||||||
|     public Int32 Capacity { get; set; } = 100_000; |     public Int32 Capacity { get; set; } = 100_000; | ||||||
| @@ -379,7 +379,7 @@ public class MemoryCache : Cache | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public override IDictionary<String, T> GetDictionary<T>(String key) |     public override IDictionary<String, T> GetDictionary<T>(String key) | ||||||
|     { |     { | ||||||
|         var item = GetOrAddItem(key, k => new ConcurrentDictionary<String, T>()); |         var item = GetOrAddItem(key, k => new NonBlockingDictionary<String, T>()); | ||||||
|         return item.Visit<IDictionary<String, T>>() ?? |         return item.Visit<IDictionary<String, T>>() ?? | ||||||
|          throw new InvalidCastException($"Unable to convert the value of [{key}] from {item.TypeCode} to {typeof(IDictionary<String, T>)}"); |          throw new InvalidCastException($"Unable to convert the value of [{key}] from {item.TypeCode} to {typeof(IDictionary<String, T>)}"); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| 后续例程与使用说明均以Redis为例,各缓存实现类似。   | 后续例程与使用说明均以Redis为例,各缓存实现类似。   | ||||||
|  |  | ||||||
| ### 内存缓存 MemoryCache | ### 内存缓存 MemoryCache | ||||||
| MemoryCache核心是并发字典ConcurrentDictionary,由于省去了序列化和网络通信,使得它具有千万级超高性能。   | MemoryCache核心是并发字典NonBlockingDictionary,由于省去了序列化和网络通信,使得它具有千万级超高性能。   | ||||||
| MemoryCache支持过期时间,默认容量10万个,未过期key超过该值后,每60秒根据LRU清理溢出部分。   | MemoryCache支持过期时间,默认容量10万个,未过期key超过该值后,每60秒根据LRU清理溢出部分。   | ||||||
| 常用于进程内千万级以下数据缓存场景。   | 常用于进程内千万级以下数据缓存场景。   | ||||||
|  |  | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ public static class CollectionHelper | |||||||
|     { |     { | ||||||
|         //if (collection == null) return null; |         //if (collection == null) return null; | ||||||
|  |  | ||||||
|         if (collection is ConcurrentDictionary<TKey, TValue> cdiv && cdiv.Keys is IList<TKey> list) return list; |         if (collection is NonBlockingDictionary<TKey, TValue> cdiv && cdiv.Keys is IList<TKey> list) return list; | ||||||
|  |  | ||||||
|         if (collection.Count == 0) return []; |         if (collection.Count == 0) return []; | ||||||
|         lock (collection) |         lock (collection) | ||||||
| @@ -65,8 +65,8 @@ public static class CollectionHelper | |||||||
|     { |     { | ||||||
|         //if (collection == null) return null; |         //if (collection == null) return null; | ||||||
|  |  | ||||||
|         //if (collection is ConcurrentDictionary<TKey, TValue> cdiv) return cdiv.Values as IList<TValue>; |         //if (collection is NonBlockingDictionary<TKey, TValue> cdiv) return cdiv.Values as IList<TValue>; | ||||||
|         if (collection is ConcurrentDictionary<TKey, TValue> cdiv && cdiv.Values is IList<TValue> list) return list; |         if (collection is NonBlockingDictionary<TKey, TValue> cdiv && cdiv.Values is IList<TValue> list) return list; | ||||||
|  |  | ||||||
|         if (collection.Count == 0) return []; |         if (collection.Count == 0) return []; | ||||||
|         lock (collection) |         lock (collection) | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ namespace ThingsGateway.NewLife.Collections; | |||||||
| /// </remarks> | /// </remarks> | ||||||
| public class ConcurrentHashSet<T> : IEnumerable<T> where T : notnull | public class ConcurrentHashSet<T> : IEnumerable<T> where T : notnull | ||||||
| { | { | ||||||
|     private readonly ConcurrentDictionary<T, Byte> _dic = new(); |     private readonly NonBlockingDictionary<T, Byte> _dic = new(); | ||||||
|  |  | ||||||
|     /// <summary>是否空集合</summary> |     /// <summary>是否空集合</summary> | ||||||
|     public Boolean IsEmpty => _dic.IsEmpty; |     public Boolean IsEmpty => _dic.IsEmpty; | ||||||
|   | |||||||
| @@ -0,0 +1,77 @@ | |||||||
|  | // Copyright (c) Vladimir Sadov. All rights reserved. | ||||||
|  | // | ||||||
|  | // This file is distributed under the MIT License. See LICENSE.md for details. | ||||||
|  |  | ||||||
|  | #nullable disable | ||||||
|  |  | ||||||
|  | namespace System.Collections.Concurrent | ||||||
|  | { | ||||||
|  |     internal abstract partial class DictionaryImpl<TKey, TKeyStore, TValue> | ||||||
|  |         : DictionaryImpl<TKey, TValue> | ||||||
|  |     { | ||||||
|  |         internal override Snapshot GetSnapshot() | ||||||
|  |         { | ||||||
|  |             return new SnapshotImpl(this); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private class SnapshotImpl : Snapshot | ||||||
|  |         { | ||||||
|  |             private readonly DictionaryImpl<TKey, TKeyStore, TValue> _table; | ||||||
|  |  | ||||||
|  |             public SnapshotImpl(DictionaryImpl<TKey, TKeyStore, TValue> dict) | ||||||
|  |             { | ||||||
|  |                 this._table = dict; | ||||||
|  |  | ||||||
|  |                 // linearization point. | ||||||
|  |                 // if table is quiescent and has no copy in progress, | ||||||
|  |                 // we can simply iterate over its table. | ||||||
|  |                 while (_table._newTable != null) | ||||||
|  |                 { | ||||||
|  |                     // there is a copy in progress, finish it and try again | ||||||
|  |                     _table.HelpCopy(copy_all: true); | ||||||
|  |                     this._table = (DictionaryImpl<TKey, TKeyStore, TValue>)this._table._topDict._table; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             public override int Count => _table.Count; | ||||||
|  |  | ||||||
|  |             public override bool MoveNext() | ||||||
|  |             { | ||||||
|  |                 var entries = this._table._entries; | ||||||
|  |                 while (_idx < entries.Length) | ||||||
|  |                 { | ||||||
|  |                     var nextEntry = entries[_idx++]; | ||||||
|  |  | ||||||
|  |                     if (nextEntry.value != null) | ||||||
|  |                     { | ||||||
|  |                         var nextKstore = nextEntry.key; | ||||||
|  |                         if (nextKstore == null) | ||||||
|  |                         { | ||||||
|  |                             // slot was deleted. | ||||||
|  |                             continue; | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         _curKey = _table.keyFromEntry(nextKstore); | ||||||
|  |                         object nextV = _table.TryGetValue(_curKey); | ||||||
|  |                         if (nextV != null) | ||||||
|  |                         { | ||||||
|  |                             _curValue = _table.FromObjectValue(nextV); | ||||||
|  |                             return true; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 _curKey = default; | ||||||
|  |                 _curValue = default; | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             public override void Reset() | ||||||
|  |             { | ||||||
|  |                 _idx = 0; | ||||||
|  |                 _curKey = default; | ||||||
|  |                 _curValue = default; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,93 @@ | |||||||
|  | // Copyright (c) Vladimir Sadov. All rights reserved. | ||||||
|  | // | ||||||
|  | // This file is distributed under the MIT License. See LICENSE.md for details. | ||||||
|  |  | ||||||
|  | #nullable disable | ||||||
|  |  | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  |  | ||||||
|  | namespace System.Collections.Concurrent | ||||||
|  | { | ||||||
|  |     internal abstract class DictionaryImpl | ||||||
|  |     { | ||||||
|  |         internal enum ValueMatch | ||||||
|  |         { | ||||||
|  |             Any,            // sets new value unconditionally, used by index set and TryRemove(key) | ||||||
|  |             NullOrDead,     // set value if original value is null or dead, used by Add/TryAdd | ||||||
|  |             NotNullOrDead,  // set value if original value is alive, used by Remove | ||||||
|  |             OldValue,       // sets new value if old value matches | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         internal sealed class Prime | ||||||
|  |         { | ||||||
|  |             internal object originalValue; | ||||||
|  |  | ||||||
|  |             public Prime(object originalValue) | ||||||
|  |             { | ||||||
|  |                 this.originalValue = originalValue; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         internal static readonly object TOMBSTONE = new object(); | ||||||
|  |         internal static readonly Prime TOMBPRIME = new Prime(TOMBSTONE); | ||||||
|  |         internal static readonly object NULLVALUE = new object(); | ||||||
|  |  | ||||||
|  |         // represents a trivially copied empty entry | ||||||
|  |         // we insert it in the old table during rehashing | ||||||
|  |         // to reduce chances that more entries are added | ||||||
|  |         protected const int TOMBPRIMEHASH = 1 << 31; | ||||||
|  |  | ||||||
|  |         // we cannot distigush zero keys from uninitialized state | ||||||
|  |         // so we force them to have this special hash instead | ||||||
|  |         protected const int ZEROHASH = 1 << 30; | ||||||
|  |  | ||||||
|  |         // all regular hashes have both these bits set | ||||||
|  |         // to be different from either 0, TOMBPRIMEHASH or ZEROHASH | ||||||
|  |         // having only these bits set in a case of Ref key means that the slot is permanently deleted. | ||||||
|  |         protected const int SPECIAL_HASH_BITS = TOMBPRIMEHASH | ZEROHASH; | ||||||
|  |  | ||||||
|  |         // Heuristic to decide if we have reprobed toooo many times.  Running over | ||||||
|  |         // the reprobe limit on a 'get' call acts as a 'miss'; on a 'put' call it | ||||||
|  |         // can trigger a table resize.  Several places must have exact agreement on | ||||||
|  |         // what the reprobe_limit is, so we share it here. | ||||||
|  |         protected const int REPROBE_LIMIT = 4; | ||||||
|  |         protected const int REPROBE_LIMIT_SHIFT = 8; | ||||||
|  |  | ||||||
|  |         protected static int ReprobeLimit(int lenMask) | ||||||
|  |         { | ||||||
|  |             // 1/2 of table with some extra | ||||||
|  |             return REPROBE_LIMIT + (lenMask >> REPROBE_LIMIT_SHIFT); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected static bool EntryValueNullOrDead(object entryValue) | ||||||
|  |         { | ||||||
|  |             return entryValue == null || entryValue == TOMBSTONE; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         protected static int ReduceHashToIndex(int fullHash, int lenMask) | ||||||
|  |         { | ||||||
|  |             var h = (uint)fullHash; | ||||||
|  |  | ||||||
|  |             // xor-shift some upper bits down, in case if variations are mostly in high bits | ||||||
|  |             // and scatter the bits a little to break up clusters if hashes are periodic (like 42, 43, 44, ...) | ||||||
|  |             // long clusters can cause long reprobes. small clusters are ok though. | ||||||
|  |             h ^= h >> 15; | ||||||
|  |             h ^= h >> 8; | ||||||
|  |             h += (h >> 3) * 2654435769u; | ||||||
|  |  | ||||||
|  |             return (int)h & lenMask; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         internal static object ToObjectValue<TValue>(TValue value) | ||||||
|  |         { | ||||||
|  |             if (default(TValue) != null) | ||||||
|  |             { | ||||||
|  |                 return new Boxed<TValue>(value); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return (object)value ?? NULLVALUE; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,138 @@ | |||||||
|  | // Copyright (c) Vladimir Sadov. All rights reserved. | ||||||
|  | // | ||||||
|  | // This file is distributed under the MIT License. See LICENSE.md for details. | ||||||
|  |  | ||||||
|  | #nullable disable | ||||||
|  |  | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  |  | ||||||
|  | namespace System.Collections.Concurrent | ||||||
|  | { | ||||||
|  |     internal sealed class DictionaryImplBoxed<TKey, TValue> | ||||||
|  |             : DictionaryImpl<TKey, Boxed<TKey>, TValue> | ||||||
|  |     { | ||||||
|  |         internal DictionaryImplBoxed(int capacity, NonBlockingDictionary<TKey, TValue> topDict) | ||||||
|  |             : base(capacity, topDict) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         internal DictionaryImplBoxed(int capacity, DictionaryImplBoxed<TKey, TValue> other) | ||||||
|  |             : base(capacity, other) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForPut(ref Boxed<TKey> entryKey, TKey key) | ||||||
|  |         { | ||||||
|  |             var entryKeyValue = entryKey; | ||||||
|  |             if (entryKeyValue == null) | ||||||
|  |             { | ||||||
|  |                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, new Boxed<TKey>(key), null); | ||||||
|  |                 if (entryKeyValue == null) | ||||||
|  |                 { | ||||||
|  |                     // claimed a new slot | ||||||
|  |                     this.allocatedSlotCount.Increment(); | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return _keyComparer.Equals(key, entryKeyValue.Value); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForCopy(ref Boxed<TKey> entryKey, Boxed<TKey> key) | ||||||
|  |         { | ||||||
|  |             var entryKeyValue = entryKey; | ||||||
|  |             if (entryKeyValue == null) | ||||||
|  |             { | ||||||
|  |                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, null); | ||||||
|  |                 if (entryKeyValue == null) | ||||||
|  |                 { | ||||||
|  |                     // claimed a new slot | ||||||
|  |                     this.allocatedSlotCount.Increment(); | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return _keyComparer.Equals(key.Value, entryKeyValue.Value); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool keyEqual(TKey key, Boxed<TKey> entryKey) | ||||||
|  |         { | ||||||
|  |             //NOTE: slots are claimed in two stages - claim a hash, then set a key | ||||||
|  |             //      it is possible to observe a slot with a null key, but with hash already set | ||||||
|  |             //      that is not a match since the key is not yet in the table | ||||||
|  |             if (entryKey == null) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return _keyComparer.Equals(key, entryKey.Value); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override DictionaryImpl<TKey, Boxed<TKey>, TValue> CreateNew(int capacity) | ||||||
|  |         { | ||||||
|  |             return new DictionaryImplBoxed<TKey, TValue>(capacity, this); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override TKey keyFromEntry(Boxed<TKey> entryKey) | ||||||
|  |         { | ||||||
|  |             return entryKey.Value; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | #pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() | ||||||
|  |     internal class Boxed<T> | ||||||
|  |     { | ||||||
|  |         // 0 - allow writes, 1 - someone is writing, 2 frozen. | ||||||
|  |         public int writeStatus; | ||||||
|  |         public T Value; | ||||||
|  |  | ||||||
|  |         public Boxed(T key) | ||||||
|  |         { | ||||||
|  |             this.Value = key; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override bool Equals(object obj) | ||||||
|  |         { | ||||||
|  |             return EqualityComparer<T>.Default.Equals(this.Value, Unsafe.As<Boxed<T>>(obj).Value); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         public bool TryVolatileWrite(T value) | ||||||
|  |         { | ||||||
|  |             if (Interlocked.CompareExchange(ref writeStatus, 1, 0) == 0) | ||||||
|  |             { | ||||||
|  |                 Value = value; | ||||||
|  |                 Volatile.Write(ref writeStatus, 0); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         public bool TryCompareExchange(T oldValue, T newValue, out bool changed) | ||||||
|  |         { | ||||||
|  |             changed = false; | ||||||
|  |             if (Interlocked.CompareExchange(ref writeStatus, 1, 0) != 0) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (EqualityComparer<T>.Default.Equals(Value, oldValue)) | ||||||
|  |             { | ||||||
|  |                 Value = newValue; | ||||||
|  |                 changed = true; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             Volatile.Write(ref writeStatus, 0); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         internal void Freeze() | ||||||
|  |         { | ||||||
|  |             // Wait for writers (1) to leave. Already 2 is ok, or set 0 -> 2. | ||||||
|  |             while (Interlocked.CompareExchange(ref writeStatus, 2, 0) == 1) ; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | #pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() | ||||||
|  | } | ||||||
| @@ -0,0 +1,143 @@ | |||||||
|  | // Copyright (c) Vladimir Sadov. All rights reserved. | ||||||
|  | // | ||||||
|  | // This file is distributed under the MIT License. See LICENSE.md for details. | ||||||
|  |  | ||||||
|  | namespace System.Collections.Concurrent | ||||||
|  | { | ||||||
|  |     internal sealed class DictionaryImplInt<TValue> | ||||||
|  |                 : DictionaryImpl<int, int, TValue> | ||||||
|  |     { | ||||||
|  |         internal DictionaryImplInt(int capacity, NonBlockingDictionary<int, TValue> topDict) | ||||||
|  |             : base(capacity, topDict) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         internal DictionaryImplInt(int capacity, DictionaryImplInt<TValue> other) | ||||||
|  |             : base(capacity, other) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForPut(ref int entryKey, int key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForCopy(ref int entryKey, int key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private bool TryClaimSlot(ref int entryKey, int key) | ||||||
|  |         { | ||||||
|  |             var entryKeyValue = entryKey; | ||||||
|  |             //zero keys are claimed via hash | ||||||
|  |             if (entryKeyValue == 0 & key != 0) | ||||||
|  |             { | ||||||
|  |                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, 0); | ||||||
|  |                 if (entryKeyValue == 0) | ||||||
|  |                 { | ||||||
|  |                     // claimed a new slot | ||||||
|  |                     this.allocatedSlotCount.Increment(); | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return key == entryKeyValue || _keyComparer.Equals(key, entryKeyValue); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override int hash(int key) | ||||||
|  |         { | ||||||
|  |             if (key == 0) | ||||||
|  |             { | ||||||
|  |                 return ZEROHASH; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return base.hash(key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool keyEqual(int key, int entryKey) | ||||||
|  |         { | ||||||
|  |             return key == entryKey || _keyComparer.Equals(key, entryKey); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override DictionaryImpl<int, int, TValue> CreateNew(int capacity) | ||||||
|  |         { | ||||||
|  |             return new DictionaryImplInt<TValue>(capacity, this); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override int keyFromEntry(int entryKey) | ||||||
|  |         { | ||||||
|  |             return entryKey; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     internal sealed class DictionaryImplIntNoComparer<TValue> | ||||||
|  |             : DictionaryImpl<int, int, TValue> | ||||||
|  |     { | ||||||
|  |         internal DictionaryImplIntNoComparer(int capacity, NonBlockingDictionary<int, TValue> topDict) | ||||||
|  |             : base(capacity, topDict) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         internal DictionaryImplIntNoComparer(int capacity, DictionaryImplIntNoComparer<TValue> other) | ||||||
|  |             : base(capacity, other) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForPut(ref int entryKey, int key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForCopy(ref int entryKey, int key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private bool TryClaimSlot(ref int entryKey, int key) | ||||||
|  |         { | ||||||
|  |             var entryKeyValue = entryKey; | ||||||
|  |             //zero keys are claimed via hash | ||||||
|  |             if (entryKeyValue == 0 & key != 0) | ||||||
|  |             { | ||||||
|  |                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, 0); | ||||||
|  |                 if (entryKeyValue == 0) | ||||||
|  |                 { | ||||||
|  |                     // claimed a new slot | ||||||
|  |                     this.allocatedSlotCount.Increment(); | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return key == entryKeyValue; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // inline the base implementation to devirtualize calls to hash and keyEqual | ||||||
|  |         internal override object TryGetValue(int key) | ||||||
|  |         { | ||||||
|  |             return base.TryGetValue(key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override int hash(int key) | ||||||
|  |         { | ||||||
|  |             return (key == 0) ? | ||||||
|  |                 ZEROHASH : | ||||||
|  |                 key | SPECIAL_HASH_BITS; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool keyEqual(int key, int entryKey) | ||||||
|  |         { | ||||||
|  |             return key == entryKey; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override DictionaryImpl<int, int, TValue> CreateNew(int capacity) | ||||||
|  |         { | ||||||
|  |             return new DictionaryImplIntNoComparer<TValue>(capacity, this); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override int keyFromEntry(int entryKey) | ||||||
|  |         { | ||||||
|  |             return entryKey; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,143 @@ | |||||||
|  | // Copyright (c) Vladimir Sadov. All rights reserved. | ||||||
|  | // | ||||||
|  | // This file is distributed under the MIT License. See LICENSE.md for details. | ||||||
|  |  | ||||||
|  | namespace System.Collections.Concurrent | ||||||
|  | { | ||||||
|  |     internal sealed class DictionaryImplLong<TValue> | ||||||
|  |                 : DictionaryImpl<long, long, TValue> | ||||||
|  |     { | ||||||
|  |         internal DictionaryImplLong(int capacity, NonBlockingDictionary<long, TValue> topDict) | ||||||
|  |             : base(capacity, topDict) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         internal DictionaryImplLong(int capacity, DictionaryImplLong<TValue> other) | ||||||
|  |             : base(capacity, other) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForPut(ref long entryKey, long key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForCopy(ref long entryKey, long key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private bool TryClaimSlot(ref long entryKey, long key) | ||||||
|  |         { | ||||||
|  |             var entryKeyValue = entryKey; | ||||||
|  |             //zero keys are claimed via hash | ||||||
|  |             if (entryKeyValue == 0 && key != 0) | ||||||
|  |             { | ||||||
|  |                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, 0); | ||||||
|  |                 if (entryKeyValue == 0) | ||||||
|  |                 { | ||||||
|  |                     // claimed a new slot | ||||||
|  |                     this.allocatedSlotCount.Increment(); | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return key == entryKeyValue || _keyComparer.Equals(key, entryKeyValue); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override int hash(long key) | ||||||
|  |         { | ||||||
|  |             if (key == 0) | ||||||
|  |             { | ||||||
|  |                 return ZEROHASH; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return base.hash(key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool keyEqual(long key, long entryKey) | ||||||
|  |         { | ||||||
|  |             return key == entryKey || _keyComparer.Equals(key, entryKey); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override DictionaryImpl<long, long, TValue> CreateNew(int capacity) | ||||||
|  |         { | ||||||
|  |             return new DictionaryImplLong<TValue>(capacity, this); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override long keyFromEntry(long entryKey) | ||||||
|  |         { | ||||||
|  |             return entryKey; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     internal sealed class DictionaryImplLongNoComparer<TValue> | ||||||
|  |             : DictionaryImpl<long, long, TValue> | ||||||
|  |     { | ||||||
|  |         internal DictionaryImplLongNoComparer(int capacity, NonBlockingDictionary<long, TValue> topDict) | ||||||
|  |             : base(capacity, topDict) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         internal DictionaryImplLongNoComparer(int capacity, DictionaryImplLongNoComparer<TValue> other) | ||||||
|  |             : base(capacity, other) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForPut(ref long entryKey, long key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForCopy(ref long entryKey, long key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private bool TryClaimSlot(ref long entryKey, long key) | ||||||
|  |         { | ||||||
|  |             var entryKeyValue = entryKey; | ||||||
|  |             //zero keys are claimed via hash | ||||||
|  |             if (entryKeyValue == 0 && key != 0) | ||||||
|  |             { | ||||||
|  |                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, 0); | ||||||
|  |                 if (entryKeyValue == 0) | ||||||
|  |                 { | ||||||
|  |                     // claimed a new slot | ||||||
|  |                     this.allocatedSlotCount.Increment(); | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return key == entryKeyValue; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // inline the base implementation to devirtualize calls to hash and keyEqual | ||||||
|  |         internal override object TryGetValue(long key) | ||||||
|  |         { | ||||||
|  |             return base.TryGetValue(key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override int hash(long key) | ||||||
|  |         { | ||||||
|  |             return (key == 0) ? | ||||||
|  |                 ZEROHASH : | ||||||
|  |                 key.GetHashCode() | SPECIAL_HASH_BITS; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool keyEqual(long key, long entryKey) | ||||||
|  |         { | ||||||
|  |             return key == entryKey; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override DictionaryImpl<long, long, TValue> CreateNew(int capacity) | ||||||
|  |         { | ||||||
|  |             return new DictionaryImplLongNoComparer<TValue>(capacity, this); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override long keyFromEntry(long entryKey) | ||||||
|  |         { | ||||||
|  |             return entryKey; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,143 @@ | |||||||
|  | // Copyright (c) Vladimir Sadov. All rights reserved. | ||||||
|  | // | ||||||
|  | // This file is distributed under the MIT License. See LICENSE.md for details. | ||||||
|  |  | ||||||
|  | namespace System.Collections.Concurrent | ||||||
|  | { | ||||||
|  |     internal sealed class DictionaryImplNint<TValue> | ||||||
|  |                 : DictionaryImpl<nint, nint, TValue> | ||||||
|  |     { | ||||||
|  |         internal DictionaryImplNint(int capacity, NonBlockingDictionary<nint, TValue> topDict) | ||||||
|  |             : base(capacity, topDict) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         internal DictionaryImplNint(int capacity, DictionaryImplNint<TValue> other) | ||||||
|  |             : base(capacity, other) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForPut(ref nint entryKey, nint key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForCopy(ref nint entryKey, nint key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private bool TryClaimSlot(ref nint entryKey, nint key) | ||||||
|  |         { | ||||||
|  |             var entryKeyValue = entryKey; | ||||||
|  |             //zero keys are claimed via hash | ||||||
|  |             if (entryKeyValue == 0 && key != 0) | ||||||
|  |             { | ||||||
|  |                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, (nint)0); | ||||||
|  |                 if (entryKeyValue == 0) | ||||||
|  |                 { | ||||||
|  |                     // claimed a new slot | ||||||
|  |                     this.allocatedSlotCount.Increment(); | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return key == entryKeyValue || _keyComparer.Equals(key, entryKeyValue); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override int hash(nint key) | ||||||
|  |         { | ||||||
|  |             if (key == 0) | ||||||
|  |             { | ||||||
|  |                 return ZEROHASH; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return base.hash(key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool keyEqual(nint key, nint entryKey) | ||||||
|  |         { | ||||||
|  |             return key == entryKey || _keyComparer.Equals(key, entryKey); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override DictionaryImpl<nint, nint, TValue> CreateNew(int capacity) | ||||||
|  |         { | ||||||
|  |             return new DictionaryImplNint<TValue>(capacity, this); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override nint keyFromEntry(nint entryKey) | ||||||
|  |         { | ||||||
|  |             return entryKey; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     internal sealed class DictionaryImplNintNoComparer<TValue> | ||||||
|  |             : DictionaryImpl<nint, nint, TValue> | ||||||
|  |     { | ||||||
|  |         internal DictionaryImplNintNoComparer(int capacity, NonBlockingDictionary<nint, TValue> topDict) | ||||||
|  |             : base(capacity, topDict) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         internal DictionaryImplNintNoComparer(int capacity, DictionaryImplNintNoComparer<TValue> other) | ||||||
|  |             : base(capacity, other) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForPut(ref nint entryKey, nint key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForCopy(ref nint entryKey, nint key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private bool TryClaimSlot(ref nint entryKey, nint key) | ||||||
|  |         { | ||||||
|  |             var entryKeyValue = entryKey; | ||||||
|  |             //zero keys are claimed via hash | ||||||
|  |             if (entryKeyValue == 0 && key != 0) | ||||||
|  |             { | ||||||
|  |                 entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, (nint)0); | ||||||
|  |                 if (entryKeyValue == 0) | ||||||
|  |                 { | ||||||
|  |                     // claimed a new slot | ||||||
|  |                     this.allocatedSlotCount.Increment(); | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return key == entryKeyValue; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // inline the base implementation to devirtualize calls to hash and keyEqual | ||||||
|  |         internal override object TryGetValue(nint key) | ||||||
|  |         { | ||||||
|  |             return base.TryGetValue(key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override int hash(nint key) | ||||||
|  |         { | ||||||
|  |             return (key == 0) ? | ||||||
|  |                 ZEROHASH : | ||||||
|  |                 key.GetHashCode() | SPECIAL_HASH_BITS; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool keyEqual(nint key, nint entryKey) | ||||||
|  |         { | ||||||
|  |             return key == entryKey; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override DictionaryImpl<nint, nint, TValue> CreateNew(int capacity) | ||||||
|  |         { | ||||||
|  |             return new DictionaryImplNintNoComparer<TValue>(capacity, this); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override nint keyFromEntry(nint entryKey) | ||||||
|  |         { | ||||||
|  |             return entryKey; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,95 @@ | |||||||
|  | // Copyright (c) Vladimir Sadov. All rights reserved. | ||||||
|  | // | ||||||
|  | // This file is distributed under the MIT License. See LICENSE.md for details. | ||||||
|  |  | ||||||
|  | #nullable disable | ||||||
|  |  | ||||||
|  | using System.Diagnostics; | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  |  | ||||||
|  | namespace System.Collections.Concurrent | ||||||
|  | { | ||||||
|  |     internal sealed class DictionaryImplRef<TKey, TKeyStore, TValue> | ||||||
|  |             : DictionaryImpl<TKey, TKey, TValue> | ||||||
|  |     { | ||||||
|  |         internal DictionaryImplRef(int capacity, NonBlockingDictionary<TKey, TValue> topDict) | ||||||
|  |             : base(capacity, topDict) | ||||||
|  |         { | ||||||
|  |             Debug.Assert(!typeof(TKey).IsValueType); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         internal DictionaryImplRef(int capacity, DictionaryImplRef<TKey, TKeyStore, TValue> other) | ||||||
|  |             : base(capacity, other) | ||||||
|  |         { | ||||||
|  |             Debug.Assert(!typeof(TKey).IsValueType); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForPut(ref TKey entryKey, TKey key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool TryClaimSlotForCopy(ref TKey entryKey, TKey key) | ||||||
|  |         { | ||||||
|  |             return TryClaimSlot(ref entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private bool TryClaimSlot(ref TKey entryKey, TKey key) | ||||||
|  |         { | ||||||
|  |             ref object keyLocation = ref Unsafe.As<TKey, object>(ref entryKey); | ||||||
|  |             object entryKeyValue = keyLocation; | ||||||
|  |             if (entryKeyValue == null) | ||||||
|  |             { | ||||||
|  |                 entryKeyValue = Interlocked.CompareExchange(ref keyLocation, key, null); | ||||||
|  |                 if (entryKeyValue == null) | ||||||
|  |                 { | ||||||
|  |                     // claimed a new slot | ||||||
|  |                     this.allocatedSlotCount.Increment(); | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return (object)key == entryKeyValue || | ||||||
|  |                 _keyComparer.Equals(key, Unsafe.As<object, TKey>(ref entryKeyValue)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // inline the base implementation to devirtualize calls to hash and keyEqual | ||||||
|  |         internal override object TryGetValue(TKey key) | ||||||
|  |         { | ||||||
|  |             return base.TryGetValue(key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override int hash(TKey key) | ||||||
|  |         { | ||||||
|  |             return base.hash(key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override bool keyEqual(TKey key, TKey entryKey) | ||||||
|  |         { | ||||||
|  |             if ((object)key == (object)entryKey) | ||||||
|  |             { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             //NOTE: slots are claimed in two stages - claim a hash, then set a key | ||||||
|  |             //      it is possible to observe a slot with a null key, but with hash already set | ||||||
|  |             //      that is not a match since the key is not yet in the table | ||||||
|  |             if (entryKey == null) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return _keyComparer.Equals(entryKey, key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override DictionaryImpl<TKey, TKey, TValue> CreateNew(int capacity) | ||||||
|  |         { | ||||||
|  |             return new DictionaryImplRef<TKey, TKeyStore, TValue>(capacity, this); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override TKey keyFromEntry(TKey entryKey) | ||||||
|  |         { | ||||||
|  |             return entryKey; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,80 @@ | |||||||
|  | // Copyright (c) Vladimir Sadov. All rights reserved. | ||||||
|  | // | ||||||
|  | // This file is distributed under the MIT License. See LICENSE.md for details. | ||||||
|  |  | ||||||
|  | #nullable disable | ||||||
|  |  | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  |  | ||||||
|  | namespace System.Collections.Concurrent | ||||||
|  | { | ||||||
|  |     internal abstract class DictionaryImpl<TKey, TValue> | ||||||
|  |         : DictionaryImpl | ||||||
|  |     { | ||||||
|  |         private readonly bool _valueIsValueType = typeof(TValue).IsValueType; | ||||||
|  |  | ||||||
|  |         internal IEqualityComparer<TKey> _keyComparer; | ||||||
|  |  | ||||||
|  |         internal abstract void Clear(); | ||||||
|  |         internal abstract int Count { get; } | ||||||
|  |  | ||||||
|  |         internal abstract object TryGetValue(TKey key); | ||||||
|  |         internal abstract bool PutIfMatch(TKey key, TValue newVal, ref TValue oldValue, ValueMatch match); | ||||||
|  |         internal abstract bool RemoveIfMatch(TKey key, ref TValue oldValue, ValueMatch match); | ||||||
|  |         internal abstract TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory); | ||||||
|  |  | ||||||
|  |         internal abstract Snapshot GetSnapshot(); | ||||||
|  |  | ||||||
|  |         internal abstract class Snapshot | ||||||
|  |         { | ||||||
|  |             protected int _idx; | ||||||
|  |             protected TKey _curKey; | ||||||
|  |             protected TValue _curValue; | ||||||
|  |  | ||||||
|  |             public abstract int Count { get; } | ||||||
|  |             public abstract bool MoveNext(); | ||||||
|  |             public abstract void Reset(); | ||||||
|  |  | ||||||
|  |             internal DictionaryEntry Entry | ||||||
|  |             { | ||||||
|  |                 get | ||||||
|  |                 { | ||||||
|  |                     return new DictionaryEntry(_curKey, _curValue); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             internal KeyValuePair<TKey, TValue> Current | ||||||
|  |             { | ||||||
|  |                 get | ||||||
|  |                 { | ||||||
|  |                     return new KeyValuePair<TKey, TValue>(this._curKey, _curValue); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         protected TValue FromObjectValue(object obj) | ||||||
|  |         { | ||||||
|  |             // regular value type | ||||||
|  |             if (default(TValue) != null) | ||||||
|  |             { | ||||||
|  |                 return Unsafe.As<Boxed<TValue>>(obj).Value; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // null | ||||||
|  |             if (obj == NULLVALUE) | ||||||
|  |             { | ||||||
|  |                 return default(TValue); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // ref type | ||||||
|  |             if (!_valueIsValueType) | ||||||
|  |             { | ||||||
|  |                 return Unsafe.As<object, TValue>(ref obj); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // nullable | ||||||
|  |             return (TValue)obj; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -0,0 +1,204 @@ | |||||||
|  | // Copyright (c) Vladimir Sadov. All rights reserved. | ||||||
|  | // | ||||||
|  | // This file is distributed under the MIT License. See LICENSE.md for details. | ||||||
|  |  | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
|  |  | ||||||
|  | namespace System.Collections.Concurrent | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Scalable 32bit counter that can be used from multiple threads. | ||||||
|  |     /// </summary> | ||||||
|  |     public sealed class Counter32 : CounterBase | ||||||
|  |     { | ||||||
|  |         private class Cell | ||||||
|  |         { | ||||||
|  |             [StructLayout(LayoutKind.Explicit, Size = CACHE_LINE * 2 - OBJ_HEADER_SIZE)] | ||||||
|  |             public struct SpacedCounter | ||||||
|  |             { | ||||||
|  |                 [FieldOffset(CACHE_LINE - OBJ_HEADER_SIZE)] | ||||||
|  |                 public int count; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             public SpacedCounter counter; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // spaced out counters | ||||||
|  |         private Cell[]? cells; | ||||||
|  |  | ||||||
|  |         // default counter | ||||||
|  |         private int count; | ||||||
|  |  | ||||||
|  |         // delayed estimated count | ||||||
|  |         private int lastCount; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Returns the value of the counter at the time of the call. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// The value may miss in-progress updates if the counter is being concurrently modified. | ||||||
|  |         /// </remarks> | ||||||
|  |         public int Value | ||||||
|  |         { | ||||||
|  |             get | ||||||
|  |             { | ||||||
|  |                 var count = this.count; | ||||||
|  |                 var cells = this.cells; | ||||||
|  |  | ||||||
|  |                 if (cells != null) | ||||||
|  |                 { | ||||||
|  |                     for (int i = 0; i < cells.Length; i++) | ||||||
|  |                     { | ||||||
|  |                         var cell = cells[i]; | ||||||
|  |                         if (cell != null) | ||||||
|  |                         { | ||||||
|  |                             count += cell.counter.count; | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             break; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return count; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Returns the approximate value of the counter at the time of the call. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// EstimatedValue could be significantly cheaper to obtain, but may be slightly delayed. | ||||||
|  |         /// </remarks> | ||||||
|  |         public int EstimatedValue | ||||||
|  |         { | ||||||
|  |             get | ||||||
|  |             { | ||||||
|  |                 if (this.cells == null) | ||||||
|  |                 { | ||||||
|  |                     return this.count; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 var curTicks = (uint)Environment.TickCount; | ||||||
|  |                 // more than a millisecond passed? | ||||||
|  |                 if (curTicks != lastCountTicks) | ||||||
|  |                 { | ||||||
|  |                     lastCountTicks = curTicks; | ||||||
|  |                     lastCount = Value; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return lastCount; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Increments the counter by 1. | ||||||
|  |         /// </summary> | ||||||
|  |         public void Increment() | ||||||
|  |         { | ||||||
|  |             int curCellCount = this.cellCount; | ||||||
|  |             var drift = increment(ref GetCountRef(curCellCount)); | ||||||
|  |  | ||||||
|  |             if (drift != 0) | ||||||
|  |             { | ||||||
|  |                 TryAddCell(curCellCount); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Decrements the counter by 1. | ||||||
|  |         /// </summary> | ||||||
|  |         public void Decrement() | ||||||
|  |         { | ||||||
|  |             int curCellCount = this.cellCount; | ||||||
|  |             var drift = decrement(ref GetCountRef(curCellCount)); | ||||||
|  |  | ||||||
|  |             if (drift != 0) | ||||||
|  |             { | ||||||
|  |                 TryAddCell(curCellCount); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Increments the counter by 'value'. | ||||||
|  |         /// </summary> | ||||||
|  |         public void Add(int value) | ||||||
|  |         { | ||||||
|  |             int curCellCount = this.cellCount; | ||||||
|  |             var drift = add(ref GetCountRef(curCellCount), value); | ||||||
|  |  | ||||||
|  |             if (drift != 0) | ||||||
|  |             { | ||||||
|  |                 TryAddCell(curCellCount); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         private ref int GetCountRef(int curCellCount) | ||||||
|  |         { | ||||||
|  |             ref var countRef = ref count; | ||||||
|  |  | ||||||
|  |             Cell[]? cells; | ||||||
|  |             if ((cells = this.cells) != null && curCellCount > 1) | ||||||
|  |             { | ||||||
|  |                 var cell = cells[GetIndex((uint)curCellCount)]; | ||||||
|  |                 if (cell != null) | ||||||
|  |                 { | ||||||
|  |                     countRef = ref cell.counter.count; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return ref countRef; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static int increment(ref int val) | ||||||
|  |         { | ||||||
|  |             return -val - 1 + Interlocked.Increment(ref val); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static int add(ref int val, int inc) | ||||||
|  |         { | ||||||
|  |             return -val - inc + Interlocked.Add(ref val, inc); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static int decrement(ref int val) | ||||||
|  |         { | ||||||
|  |             return val - 1 - Interlocked.Decrement(ref val); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void TryAddCell(int curCellCount) | ||||||
|  |         { | ||||||
|  |             if (curCellCount < s_MaxCellCount) | ||||||
|  |             { | ||||||
|  |                 TryAddCellCore(curCellCount); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [MethodImpl(MethodImplOptions.NoInlining)] | ||||||
|  |         private void TryAddCellCore(int curCellCount) | ||||||
|  |         { | ||||||
|  |             var cells = this.cells; | ||||||
|  |             if (cells == null) | ||||||
|  |             { | ||||||
|  |                 var newCells = new Cell[s_MaxCellCount]; | ||||||
|  |                 cells = Interlocked.CompareExchange(ref this.cells, newCells, null) ?? newCells; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (cells[curCellCount] == null) | ||||||
|  |             { | ||||||
|  |                 Interlocked.CompareExchange(ref cells[curCellCount], new Cell(), null); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (this.cellCount == curCellCount) | ||||||
|  |             { | ||||||
|  |                 Interlocked.CompareExchange(ref this.cellCount, curCellCount + 1, curCellCount); | ||||||
|  |                 //if (Interlocked.CompareExchange(ref this.cellCount, curCellCount + 1, curCellCount) == curCellCount) | ||||||
|  |                 //{ | ||||||
|  |                 //    System.Console.WriteLine(curCellCount + 1); | ||||||
|  |                 //} | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,203 @@ | |||||||
|  | // Copyright (c) Vladimir Sadov. All rights reserved. | ||||||
|  | // | ||||||
|  | // This file is distributed under the MIT License. See LICENSE.md for details. | ||||||
|  |  | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
|  |  | ||||||
|  | namespace System.Collections.Concurrent | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Scalable 64bit counter that can be used from multiple threads. | ||||||
|  |     /// </summary> | ||||||
|  |     public sealed class Counter64 : CounterBase | ||||||
|  |     { | ||||||
|  |         private class Cell | ||||||
|  |         { | ||||||
|  |             [StructLayout(LayoutKind.Explicit, Size = CACHE_LINE * 2 - OBJ_HEADER_SIZE)] | ||||||
|  |             public struct SpacedCounter | ||||||
|  |             { | ||||||
|  |                 [FieldOffset(CACHE_LINE - OBJ_HEADER_SIZE)] | ||||||
|  |                 public long count; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             public SpacedCounter counter; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // spaced out counters | ||||||
|  |         private Cell[]? cells; | ||||||
|  |  | ||||||
|  |         // default counter | ||||||
|  |         private long count; | ||||||
|  |  | ||||||
|  |         // delayed count | ||||||
|  |         private long lastCount; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Returns the value of the counter at the time of the call. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// The value may miss in-progress updates if the counter is being concurrently modified. | ||||||
|  |         /// </remarks> | ||||||
|  |         public long Value | ||||||
|  |         { | ||||||
|  |             get | ||||||
|  |             { | ||||||
|  |                 var count = this.count; | ||||||
|  |                 var cells = this.cells; | ||||||
|  |  | ||||||
|  |                 if (cells != null) | ||||||
|  |                 { | ||||||
|  |                     for (int i = 0; i < cells.Length; i++) | ||||||
|  |                     { | ||||||
|  |                         var cell = cells[i]; | ||||||
|  |                         if (cell != null) | ||||||
|  |                         { | ||||||
|  |                             count += cell.counter.count; | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             break; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return count; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Returns the approximate value of the counter at the time of the call. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// EstimatedValue could be significantly cheaper to obtain, but may be slightly delayed. | ||||||
|  |         /// </remarks> | ||||||
|  |         public long EstimatedValue | ||||||
|  |         { | ||||||
|  |             get | ||||||
|  |             { | ||||||
|  |                 if (this.cellCount == 0) | ||||||
|  |                 { | ||||||
|  |                     return Value; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 var curTicks = (uint)Environment.TickCount; | ||||||
|  |                 // more than a millisecond passed? | ||||||
|  |                 if (curTicks != lastCountTicks) | ||||||
|  |                 { | ||||||
|  |                     lastCountTicks = curTicks; | ||||||
|  |                     lastCount = Value; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return lastCount; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Increments the counter by 1. | ||||||
|  |         /// </summary> | ||||||
|  |         public void Increment() | ||||||
|  |         { | ||||||
|  |             int curCellCount = this.cellCount; | ||||||
|  |             var drift = increment(ref GetCountRef(curCellCount)); | ||||||
|  |  | ||||||
|  |             if (drift != 0) | ||||||
|  |             { | ||||||
|  |                 TryAddCell(curCellCount); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Decrements the counter by 1. | ||||||
|  |         /// </summary> | ||||||
|  |         public void Decrement() | ||||||
|  |         { | ||||||
|  |             int curCellCount = this.cellCount; | ||||||
|  |             var drift = decrement(ref GetCountRef(curCellCount)); | ||||||
|  |  | ||||||
|  |             if (drift != 0) | ||||||
|  |             { | ||||||
|  |                 TryAddCell(curCellCount); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Increments the counter by 'value'. | ||||||
|  |         /// </summary> | ||||||
|  |         public void Add(int value) | ||||||
|  |         { | ||||||
|  |             int curCellCount = this.cellCount; | ||||||
|  |             var drift = add(ref GetCountRef(curCellCount), value); | ||||||
|  |  | ||||||
|  |             if (drift != 0) | ||||||
|  |             { | ||||||
|  |                 TryAddCell(curCellCount); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         private ref long GetCountRef(int curCellCount) | ||||||
|  |         { | ||||||
|  |             ref var countRef = ref count; | ||||||
|  |  | ||||||
|  |             Cell[]? cells; | ||||||
|  |             if ((cells = this.cells) != null && curCellCount > 1) | ||||||
|  |             { | ||||||
|  |                 var cell = cells[GetIndex((uint)curCellCount)]; | ||||||
|  |                 if (cell != null) | ||||||
|  |                 { | ||||||
|  |                     countRef = ref cell.counter.count; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return ref countRef; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static long increment(ref long val) | ||||||
|  |         { | ||||||
|  |             return -val - 1 + Interlocked.Increment(ref val); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static long add(ref long val, int inc) | ||||||
|  |         { | ||||||
|  |             return -val - inc + Interlocked.Add(ref val, inc); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static long decrement(ref long val) | ||||||
|  |         { | ||||||
|  |             return val - 1 - Interlocked.Decrement(ref val); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void TryAddCell(int curCellCount) | ||||||
|  |         { | ||||||
|  |             if (curCellCount < s_MaxCellCount) | ||||||
|  |             { | ||||||
|  |                 TryAddCellCore(curCellCount); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void TryAddCellCore(int curCellCount) | ||||||
|  |         { | ||||||
|  |             var cells = this.cells; | ||||||
|  |             if (cells == null) | ||||||
|  |             { | ||||||
|  |                 var newCells = new Cell[s_MaxCellCount]; | ||||||
|  |                 cells = Interlocked.CompareExchange(ref this.cells, newCells, null) ?? newCells; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (cells[curCellCount] == null) | ||||||
|  |             { | ||||||
|  |                 Interlocked.CompareExchange(ref cells[curCellCount], new Cell(), null); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (this.cellCount == curCellCount) | ||||||
|  |             { | ||||||
|  |                 Interlocked.CompareExchange(ref this.cellCount, curCellCount + 1, curCellCount); | ||||||
|  |                 //if (Interlocked.CompareExchange(ref this.cellCount, curCellCount + 1, curCellCount) == curCellCount) | ||||||
|  |                 //{ | ||||||
|  |                 //    System.Console.WriteLine(curCellCount + 1); | ||||||
|  |                 //} | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,38 @@ | |||||||
|  | // Copyright (c) Vladimir Sadov. All rights reserved. | ||||||
|  | // | ||||||
|  | // This file is distributed under the MIT License. See LICENSE.md for details. | ||||||
|  |  | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  |  | ||||||
|  | namespace System.Collections.Concurrent | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Scalable counter base. | ||||||
|  |     /// </summary> | ||||||
|  |     public class CounterBase | ||||||
|  |     { | ||||||
|  |         private protected const int CACHE_LINE = 64; | ||||||
|  |         private protected const int OBJ_HEADER_SIZE = 8; | ||||||
|  |  | ||||||
|  |         private protected static readonly int s_MaxCellCount = Util.AlignToPowerOfTwo(Environment.ProcessorCount) + 1; | ||||||
|  |  | ||||||
|  |         // how many cells we have | ||||||
|  |         private protected int cellCount; | ||||||
|  |  | ||||||
|  |         // delayed count time | ||||||
|  |         private protected uint lastCountTicks; | ||||||
|  |  | ||||||
|  |         private protected CounterBase() | ||||||
|  |         { | ||||||
|  |             // touch static | ||||||
|  |             _ = s_MaxCellCount; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         private protected unsafe static int GetIndex(uint cellCount) | ||||||
|  |         { | ||||||
|  |             nuint addr = (nuint)(&cellCount); | ||||||
|  |             return (int)(addr % cellCount); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | using System.Diagnostics; | ||||||
|  |  | ||||||
|  | namespace System.Collections.Concurrent | ||||||
|  | { | ||||||
|  |     internal static class Util | ||||||
|  |     { | ||||||
|  |         // returns 2^x >= size | ||||||
|  |         internal static int AlignToPowerOfTwo(int size) | ||||||
|  |         { | ||||||
|  |             Debug.Assert(size > 0); | ||||||
|  |  | ||||||
|  |             size--; | ||||||
|  |             size |= size >> 1; | ||||||
|  |             size |= size >> 2; | ||||||
|  |             size |= size >> 4; | ||||||
|  |             size |= size >> 8; | ||||||
|  |             size |= size >> 16; | ||||||
|  |             return size + 1; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -31,8 +31,8 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull | |||||||
|     /// <summary>最小个数。默认1</summary> |     /// <summary>最小个数。默认1</summary> | ||||||
|     public Int32 Min { get; set; } = 1; |     public Int32 Min { get; set; } = 1; | ||||||
|  |  | ||||||
|     /// <summary>空闲清理时间。最小个数之上的资源超过空闲时间时被清理,默认10s</summary> |     /// <summary>空闲清理时间。最小个数之上的资源超过空闲时间时被清理,默认60s</summary> | ||||||
|     public Int32 IdleTime { get; set; } = 10; |     public Int32 IdleTime { get; set; } = 60; | ||||||
|  |  | ||||||
|     /// <summary>完全空闲清理时间。最小个数之下的资源超过空闲时间时被清理,默认0s永不清理</summary> |     /// <summary>完全空闲清理时间。最小个数之下的资源超过空闲时间时被清理,默认0s永不清理</summary> | ||||||
|     public Int32 AllIdleTime { get; set; } = 0; |     public Int32 AllIdleTime { get; set; } = 0; | ||||||
| @@ -44,7 +44,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull | |||||||
|     private readonly ConcurrentQueue<Item> _free2 = new(); |     private readonly ConcurrentQueue<Item> _free2 = new(); | ||||||
|  |  | ||||||
|     /// <summary>借出去的放在这</summary> |     /// <summary>借出去的放在这</summary> | ||||||
|     private readonly ConcurrentDictionary<T, Item> _busy = new(); |     private readonly NonBlockingDictionary<T, Item> _busy = new(); | ||||||
|  |  | ||||||
|     //private readonly Object SyncRoot = new(); |     //private readonly Object SyncRoot = new(); | ||||||
|     #endregion |     #endregion | ||||||
| @@ -126,9 +126,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull | |||||||
|                 if (Max > 0 && count >= Max) |                 if (Max > 0 && count >= Max) | ||||||
|                 { |                 { | ||||||
|                     var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}"; |                     var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}"; | ||||||
|  |  | ||||||
|                     WriteLog("Acquire Max " + msg); |                     WriteLog("Acquire Max " + msg); | ||||||
|  |  | ||||||
|                     throw new Exception(Name + " " + msg); |                     throw new Exception(Name + " " + msg); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
| @@ -268,7 +266,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull | |||||||
|         { |         { | ||||||
|             if (_timer != null) return; |             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; |         var count = 0; | ||||||
|  |  | ||||||
|         // 清理过期不还。避免有借没还 |         // 清理过期不还。避免有借没还 | ||||||
|         if (!_busy.IsEmpty) |         if (AllIdleTime > 0 && !_busy.IsEmpty) | ||||||
|         { |         { | ||||||
|             var exp = TimerX.Now.AddSeconds(-AllIdleTime); |             var exp = TimerX.Now.AddSeconds(-AllIdleTime); | ||||||
|             foreach (var item in _busy) |             foreach (var item in _busy) | ||||||
|   | |||||||
							
								
								
									
										241
									
								
								src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,241 @@ | |||||||
|  | using ThingsGateway.NewLife.Log; | ||||||
|  | using ThingsGateway.NewLife.Reflection; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.NewLife.Collections; | ||||||
|  |  | ||||||
|  | /// <summary>资源池。支持空闲释放,主要用于数据库连接池和网络连接池</summary> | ||||||
|  | /// <remarks> | ||||||
|  | /// 文档 https://newlifex.com/core/object_pool | ||||||
|  | /// </remarks> | ||||||
|  | /// <typeparam name="T"></typeparam> | ||||||
|  | public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class | ||||||
|  | { | ||||||
|  |     #region 属性 | ||||||
|  |     /// <summary>名称</summary> | ||||||
|  |     public String Name { get; set; } | ||||||
|  |  | ||||||
|  |     private Int32 _FreeCount; | ||||||
|  |     /// <summary>空闲个数</summary> | ||||||
|  |     public Int32 FreeCount => _FreeCount; | ||||||
|  |  | ||||||
|  |     private Int32 _BusyCount; | ||||||
|  |     /// <summary>繁忙个数</summary> | ||||||
|  |     public Int32 BusyCount => _BusyCount; | ||||||
|  |  | ||||||
|  |     /// <summary>最大个数。默认0,0表示无上限</summary> | ||||||
|  |     public Int32 Max { get; set; } = 0; | ||||||
|  |  | ||||||
|  |     private readonly object _syncRoot = new(); | ||||||
|  |  | ||||||
|  |     /// <summary>基础空闲集合。只保存最小个数,最热部分</summary> | ||||||
|  |     private readonly Stack<T> _free = new(); | ||||||
|  |  | ||||||
|  |     /// <summary>借出去的放在这</summary> | ||||||
|  |     private readonly HashSet<T> _busy = new(); | ||||||
|  |  | ||||||
|  |     //private readonly Object SyncRoot = new(); | ||||||
|  |     #endregion | ||||||
|  |  | ||||||
|  |     #region 构造 | ||||||
|  |     /// <summary>实例化一个资源池</summary> | ||||||
|  |     public ObjectPoolLock() | ||||||
|  |     { | ||||||
|  |         var str = GetType().Name; | ||||||
|  |         if (str.Contains('`')) str = str.Substring(null, "`"); | ||||||
|  |         if (str != "Pool") | ||||||
|  |             Name = str; | ||||||
|  |         else | ||||||
|  |             Name = $"Pool<{typeof(T).Name}>"; | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |     ~ObjectPoolLock() | ||||||
|  |     { | ||||||
|  |         this.TryDispose(); | ||||||
|  |     } | ||||||
|  |     /// <summary>销毁</summary> | ||||||
|  |     /// <param name="disposing"></param> | ||||||
|  |     protected override void Dispose(Boolean disposing) | ||||||
|  |     { | ||||||
|  |         base.Dispose(disposing); | ||||||
|  |  | ||||||
|  |         WriteLog($"Dispose {typeof(T).FullName} FreeCount={FreeCount:n0} BusyCount={BusyCount:n0}"); | ||||||
|  |  | ||||||
|  |         Clear(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private volatile Boolean _inited; | ||||||
|  |     private void Init() | ||||||
|  |     { | ||||||
|  |         if (_inited) return; | ||||||
|  |  | ||||||
|  |         lock (lockThis) | ||||||
|  |         { | ||||||
|  |             if (_inited) return; | ||||||
|  |             _inited = true; | ||||||
|  |  | ||||||
|  |             WriteLog($"Init {typeof(T).FullName} Max={Max}"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     #endregion | ||||||
|  |  | ||||||
|  |     #region 主方法 | ||||||
|  |     /// <summary>借出</summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public virtual T Get() | ||||||
|  |     { | ||||||
|  |         T? pi = null; | ||||||
|  |         do | ||||||
|  |         { | ||||||
|  |             lock (_syncRoot) | ||||||
|  |             { | ||||||
|  |                 if (_free.Count > 0) | ||||||
|  |                 { | ||||||
|  |                     pi = _free.Pop(); | ||||||
|  |                     _FreeCount--; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     if (Max > 0 && BusyCount >= Max) | ||||||
|  |                     { | ||||||
|  |                         var msg = $"申请失败,已有 {BusyCount:n0} 达到或超过最大值 {Max:n0}"; | ||||||
|  |                         WriteLog("Acquire Max " + msg); | ||||||
|  |                         throw new Exception(Name + " " + msg); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     pi = OnCreate(); | ||||||
|  |                     if (BusyCount == 0) Init(); | ||||||
|  |  | ||||||
|  | #if DEBUG | ||||||
|  |                     WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, BusyCount + 1); | ||||||
|  | #endif | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 如果拿到的对象不可用,则重新借 | ||||||
|  |         } while (pi == null || !OnGet(pi)); | ||||||
|  |  | ||||||
|  |         lock (_syncRoot) | ||||||
|  |         { | ||||||
|  |             // 加入繁忙集合 | ||||||
|  |             _busy.Add(pi); | ||||||
|  |  | ||||||
|  |             _BusyCount++; | ||||||
|  |         } | ||||||
|  |         return pi; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary>借出时是否可用</summary> | ||||||
|  |     /// <param name="value"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     protected virtual Boolean OnGet(T value) => true; | ||||||
|  |  | ||||||
|  |     /// <summary>申请资源包装项,Dispose时自动归还到池中</summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public PoolItem<T> GetItem() => new(this, Get()); | ||||||
|  |  | ||||||
|  |     /// <summary>归还</summary> | ||||||
|  |     /// <param name="value"></param> | ||||||
|  |     public virtual Boolean Return(T value) | ||||||
|  |     { | ||||||
|  |         if (value == null) return false; | ||||||
|  |         lock (_syncRoot) | ||||||
|  |         { | ||||||
|  |             // 从繁忙队列找到并移除缓存项 | ||||||
|  |             if (!_busy.Remove(value)) | ||||||
|  |             { | ||||||
|  | #if DEBUG | ||||||
|  |                 WriteLog("Return Error"); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _BusyCount--; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 是否可用 | ||||||
|  |         if (!OnReturn(value)) | ||||||
|  |         { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (value is DisposeBase db && db.Disposed) | ||||||
|  |         { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         lock (_syncRoot) | ||||||
|  |         { | ||||||
|  |             _free.Push(value); | ||||||
|  |             _FreeCount++; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary>归还时是否可用</summary> | ||||||
|  |     /// <param name="value"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     protected virtual Boolean OnReturn(T value) => true; | ||||||
|  |  | ||||||
|  |     /// <summary>清空已有对象</summary> | ||||||
|  |     public virtual Int32 Clear() | ||||||
|  |     { | ||||||
|  |         var count = _FreeCount + _BusyCount; | ||||||
|  |  | ||||||
|  |         //_busy.Clear(); | ||||||
|  |         //_BusyCount = 0; | ||||||
|  |  | ||||||
|  |         //_free.Clear(); | ||||||
|  |         //while (_free2.TryDequeue(out var rs)) ; | ||||||
|  |         //_FreeCount = 0; | ||||||
|  |  | ||||||
|  |         lock (_syncRoot) | ||||||
|  |         { | ||||||
|  |             count = _FreeCount + _BusyCount; | ||||||
|  |  | ||||||
|  |             while (_free.Count > 0) | ||||||
|  |             { | ||||||
|  |                 var pi = _free.Pop(); | ||||||
|  |                 OnDispose(pi); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _FreeCount = 0; | ||||||
|  |  | ||||||
|  |             foreach (var item in _busy) | ||||||
|  |             { | ||||||
|  |                 OnDispose(item); | ||||||
|  |             } | ||||||
|  |             _busy.Clear(); | ||||||
|  |             _BusyCount = 0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return count; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary>销毁</summary> | ||||||
|  |     /// <param name="value"></param> | ||||||
|  |     protected virtual void OnDispose(T? value) => value.TryDispose(); | ||||||
|  |     #endregion | ||||||
|  |  | ||||||
|  |     #region 重载 | ||||||
|  |     /// <summary>创建实例</summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     protected virtual T? OnCreate() => (T?)typeof(T).CreateInstance(); | ||||||
|  |     #endregion | ||||||
|  |     protected object lockThis = new(); | ||||||
|  |  | ||||||
|  |     #region 日志 | ||||||
|  |     /// <summary>日志</summary> | ||||||
|  |     public ILog Log { get; set; } = Logger.Null; | ||||||
|  |  | ||||||
|  |     /// <summary>写日志</summary> | ||||||
|  |     /// <param name="format"></param> | ||||||
|  |     /// <param name="args"></param> | ||||||
|  |     public void WriteLog(String format, params Object?[] args) | ||||||
|  |     { | ||||||
|  |         if (Log?.Enable != true) return; | ||||||
|  |  | ||||||
|  |         Log.Info(Name + "." + format, args); | ||||||
|  |     } | ||||||
|  |     #endregion | ||||||
|  | } | ||||||
| @@ -0,0 +1,48 @@ | |||||||
|  | namespace ThingsGateway.NewLife.Collections; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | using System; | ||||||
|  | using System.Diagnostics; | ||||||
|  |  | ||||||
|  | internal struct ValueStopwatch | ||||||
|  | { | ||||||
|  | #if !NET7_0_OR_GREATER | ||||||
|  |     private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |     private readonly long _startTimestamp; | ||||||
|  |  | ||||||
|  |     public bool IsActive => _startTimestamp != 0; | ||||||
|  |  | ||||||
|  |     private ValueStopwatch(long startTimestamp) | ||||||
|  |     { | ||||||
|  |         _startTimestamp = startTimestamp; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp()); | ||||||
|  |  | ||||||
|  |     public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) | ||||||
|  |     { | ||||||
|  | #if !NET7_0_OR_GREATER | ||||||
|  |         var timestampDelta = endingTimestamp - startingTimestamp; | ||||||
|  |         var ticks = (long)(TimestampToTicks * timestampDelta); | ||||||
|  |         return new TimeSpan(ticks); | ||||||
|  | #else | ||||||
|  |         return Stopwatch.GetElapsedTime(startingTimestamp, endingTimestamp); | ||||||
|  | #endif | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public TimeSpan GetElapsedTime() | ||||||
|  |     { | ||||||
|  |         // Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0. | ||||||
|  |         // So it being 0 is a clear indication of default(ValueStopwatch) | ||||||
|  |         if (!IsActive) | ||||||
|  |         { | ||||||
|  |             throw new InvalidOperationException("An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time."); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var end = Stopwatch.GetTimestamp(); | ||||||
|  |  | ||||||
|  |         return GetElapsedTime(_startTimestamp, end); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -5,30 +5,103 @@ namespace ThingsGateway.NewLife; | |||||||
|  |  | ||||||
| public class ExpiringDictionary<TKey, TValue> : IDisposable | public class ExpiringDictionary<TKey, TValue> : IDisposable | ||||||
| { | { | ||||||
|     private ConcurrentDictionary<TKey, TValue> _dict = new(); |     /// <summary>缓存项</summary> | ||||||
|     private readonly TimerX _cleanupTimer; |     public class CacheItem | ||||||
|  |  | ||||||
|     public ExpiringDictionary(int cleanupInterval = 60000) |  | ||||||
|     { |     { | ||||||
|         _cleanupTimer = new TimerX(Clear, null, cleanupInterval, cleanupInterval) { Async = true }; |         private TValue? _value; | ||||||
|  |         /// <summary>数值</summary> | ||||||
|  |         public TValue? Value { get => _value; } | ||||||
|  |  | ||||||
|  |         /// <summary>过期时间。系统启动以来的毫秒数</summary> | ||||||
|  |         public Int64 ExpiredTime { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary>是否过期</summary> | ||||||
|  |         public Boolean Expired => ExpiredTime <= Runtime.TickCount64; | ||||||
|  |  | ||||||
|  |         /// <summary>访问时间</summary> | ||||||
|  |         public Int64 VisitTime { get; private set; } | ||||||
|  |  | ||||||
|  |         /// <summary>构造缓存项</summary> | ||||||
|  |         /// <param name="value"></param> | ||||||
|  |         /// <param name="expire"></param> | ||||||
|  |         public CacheItem(TValue? value, Int32 expire) => Set(value, expire); | ||||||
|  |  | ||||||
|  |         /// <summary>设置数值和过期时间</summary> | ||||||
|  |         /// <param name="value"></param> | ||||||
|  |         /// <param name="expire">过期时间,秒</param> | ||||||
|  |         public void Set(TValue value, Int32 expire) | ||||||
|  |         { | ||||||
|  |             _value = value; | ||||||
|  |  | ||||||
|  |             var now = VisitTime = Runtime.TickCount64; | ||||||
|  |             if (expire <= 0) | ||||||
|  |                 ExpiredTime = Int64.MaxValue; | ||||||
|  |             else | ||||||
|  |                 ExpiredTime = now + expire * 1000L; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary>更新访问时间并返回数值</summary> | ||||||
|  |         /// <returns></returns> | ||||||
|  |         public TValue? Visit() | ||||||
|  |         { | ||||||
|  |             VisitTime = Runtime.TickCount64; | ||||||
|  |             var rs = _value; | ||||||
|  |             if (rs == null) return default; | ||||||
|  |  | ||||||
|  |             return rs; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     private NonBlockingDictionary<TKey, CacheItem> _dict; | ||||||
|  |  | ||||||
|  |     private readonly TimerX _cleanupTimer; | ||||||
|  |     private int defaultExpire = 60; | ||||||
|  |     IEqualityComparer<TKey>? comparer; | ||||||
|  |     public ExpiringDictionary(int expire = 60, IEqualityComparer<TKey>? comparer = null) | ||||||
|  |     { | ||||||
|  |         defaultExpire = expire; | ||||||
|  |         this.comparer = comparer; | ||||||
|  |         _dict = new NonBlockingDictionary<TKey, CacheItem>(comparer); | ||||||
|  |  | ||||||
|  |         _cleanupTimer = new TimerX(TimerClear, null, 60000, 60000) { Async = true }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void TryAdd(TKey key, TValue value) |     public bool TryAdd(TKey key, TValue value) | ||||||
|     { |     { | ||||||
|         _dict.TryAdd(key, value); |         if (_dict.TryGetValue(key, out var item)) | ||||||
|  |         { | ||||||
|  |             if (!item.Expired) return false; | ||||||
|  |             item.Set(value, defaultExpire); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         return _dict.TryAdd(key, new CacheItem(value, defaultExpire)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public bool TryGetValue(TKey key, out TValue value) |     public bool TryGetValue(TKey key, out TValue value) | ||||||
|     { |     { | ||||||
|         return _dict.TryGetValue(key, out value); |         value = default; | ||||||
|  |  | ||||||
|  |         // 没有值,直接结束 | ||||||
|  |         if (!_dict.TryGetValue(key, out var item) || item == null) return false; | ||||||
|  |  | ||||||
|  |         // 得到已有值 | ||||||
|  |         value = item.Visit(); | ||||||
|  |  | ||||||
|  |         // 是否未过期的有效值 | ||||||
|  |         return !item.Expired; | ||||||
|     } |     } | ||||||
|     public TValue GetOrAdd(TKey key, Func<TKey, TValue> func) |     public TValue GetOrAdd(TKey key, Func<TKey, TValue> func) | ||||||
|     { |     { | ||||||
|         return _dict.GetOrAdd(key, func); |         CacheItem? item = null; | ||||||
|     } |         do | ||||||
|     public TValue GetOrAdd(TKey key, TValue value) |         { | ||||||
|     { |             if (_dict.TryGetValue(key, out item) && item != null) return item.Visit(); | ||||||
|         return _dict.GetOrAdd(key, value); |  | ||||||
|  |             item ??= new CacheItem(func(key), defaultExpire); | ||||||
|  |         } | ||||||
|  |         while (!_dict.TryAdd(key, item)); | ||||||
|  |  | ||||||
|  |         return item.Visit(); | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public bool TryRemove(TKey key) => _dict.TryRemove(key, out _); |     public bool TryRemove(TKey key) => _dict.TryRemove(key, out _); | ||||||
| @@ -38,13 +111,38 @@ public class ExpiringDictionary<TKey, TValue> : IDisposable | |||||||
|     private void Clear(object? state) |     private void Clear(object? state) | ||||||
|     { |     { | ||||||
|         var data = _dict; |         var data = _dict; | ||||||
|         _dict = new(); |         _dict = new(comparer); | ||||||
|         data.Clear(); |         data.Clear(); | ||||||
|     } |     } | ||||||
|  |     private void TimerClear(object? state) | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |         var dic = _dict; | ||||||
|  |         if (dic.IsEmpty) return; | ||||||
|  |  | ||||||
|  |         // 60分钟之内过期的数据,进入LRU淘汰 | ||||||
|  |         var now = Runtime.TickCount64; | ||||||
|  |  | ||||||
|  |         // 这里先计算,性能很重要 | ||||||
|  |         var toDels = new List<TKey>(); | ||||||
|  |         foreach (var item in dic) | ||||||
|  |         { | ||||||
|  |             // 已过期,准备删除 | ||||||
|  |             var ci = item.Value; | ||||||
|  |             if (ci.ExpiredTime <= now) | ||||||
|  |                 toDels.Add(item.Key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 确认删除 | ||||||
|  |         foreach (var item in toDels) | ||||||
|  |         { | ||||||
|  |             _dict.Remove(item); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|     public void Dispose() |     public void Dispose() | ||||||
|     { |     { | ||||||
|         _dict.Clear(); |         _dict.Clear(); | ||||||
|         _cleanupTimer.Dispose(); |         _cleanupTimer.Dispose(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,8 +13,8 @@ public class FastMapperOption | |||||||
| public static class FastMapper | public static class FastMapper | ||||||
| { | { | ||||||
|     // 泛型 + 非泛型共用缓存 |     // 泛型 + 非泛型共用缓存 | ||||||
|     private static readonly ConcurrentDictionary<(Type Source, Type Target), Delegate> _mapCache |     private static readonly NonBlockingDictionary<(Type Source, Type Target), Delegate> _mapCache | ||||||
|         = new ConcurrentDictionary<(Type, Type), Delegate>(); |         = new NonBlockingDictionary<(Type, Type), Delegate>(); | ||||||
|  |  | ||||||
|     #region 泛型入口 |     #region 泛型入口 | ||||||
|     public static TTarget Mapper<TSource, TTarget>(TSource source, FastMapperOption option = null) |     public static TTarget Mapper<TSource, TTarget>(TSource source, FastMapperOption option = null) | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Foundation; | namespace ThingsGateway.NewLife; | ||||||
| 
 | 
 | ||||||
| public class LinkedCancellationTokenSourceCache : IDisposable | public class LinkedCancellationTokenSourceCache : IDisposable | ||||||
| { | { | ||||||
| @@ -63,6 +63,7 @@ public class LinkedCancellationTokenSourceCache : IDisposable | |||||||
|             _cachedCts?.Dispose(); |             _cachedCts?.Dispose(); | ||||||
|             _cachedCts = null!; |             _cachedCts = null!; | ||||||
|         } |         } | ||||||
|  |         GC.SuppressFinalize(this); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -10,13 +10,18 @@ | |||||||
| //  感谢您的下载和使用 | //  感谢您的下载和使用 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Foundation; | namespace ThingsGateway.NewLife; | ||||||
| 
 | 
 | ||||||
| using System; | using System; | ||||||
| using System.Threading; | using System.Threading; | ||||||
| 
 | 
 | ||||||
| public sealed class ReusableCancellationTokenSource : IDisposable | public sealed class ReusableCancellationTokenSource : IDisposable | ||||||
| { | { | ||||||
|  |     ~ReusableCancellationTokenSource() | ||||||
|  |     { | ||||||
|  |         Dispose(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private readonly Timer _timer; |     private readonly Timer _timer; | ||||||
|     private CancellationTokenSource? _cts; |     private CancellationTokenSource? _cts; | ||||||
| 
 | 
 | ||||||
| @@ -47,7 +52,7 @@ public sealed class ReusableCancellationTokenSource : IDisposable | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取一个 CTS,并启动超时 |     /// 获取一个 CTS,并启动超时 | ||||||
|     /// </summary> |     /// </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; |         TimeoutStatus = false; | ||||||
| 
 | 
 | ||||||
| @@ -57,7 +62,7 @@ public sealed class ReusableCancellationTokenSource : IDisposable | |||||||
|         // 启动 Timer |         // 启动 Timer | ||||||
|         _timer.Change(timeout, Timeout.Infinite); |         _timer.Change(timeout, Timeout.Infinite); | ||||||
| 
 | 
 | ||||||
|         return _cts; |         return _cts.Token; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @@ -71,15 +76,16 @@ public sealed class ReusableCancellationTokenSource : IDisposable | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     public void Cancel() |     public void Cancel() | ||||||
|     { |     { | ||||||
|         _cts?.SafeCancel(); |         try { _cts?.Cancel(); } catch { } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void Dispose() |     public void Dispose() | ||||||
|     { |     { | ||||||
|         _cts?.SafeCancel(); |         try { _cts?.Cancel(); } catch { } | ||||||
|         _cts?.SafeDispose(); |         try { _cts?.Dispose(); } catch { } | ||||||
|         _linkedCtsCache.SafeDispose(); |         try { _linkedCtsCache?.Dispose(); } catch { } | ||||||
|         _timer.SafeDispose(); |         try { _timer?.Dispose(); } catch { } | ||||||
|  |         GC.SuppressFinalize(this); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -8,8 +8,6 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| using ThingsGateway.NewLife.Log; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.NewLife; | namespace ThingsGateway.NewLife; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -92,21 +90,119 @@ public sealed class WaitLock : IDisposable | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     public Task WaitAsync(CancellationToken cancellationToken = default) |     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> | ||||||
|     /// 进入锁 |     /// 进入锁 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public Task<bool> WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken = default) |     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); |         return _waiterLock.WaitAsync(millisecondsTimeout, cancellationToken); | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool DisposedValue; |     bool DisposedValue; | ||||||
|     public void Dispose() |     public void Dispose() | ||||||
|     { |     { | ||||||
|         DisposedValue = true; |         DisposedValue = true; | ||||||
|  | #if NET6_0_OR_GREATER | ||||||
|  |         //_reusableTimeouts?.TryDispose(); | ||||||
|  | #endif | ||||||
|         _waiterLock?.TryDispose(); |         _waiterLock?.TryDispose(); | ||||||
|         GC.SuppressFinalize(this); |         GC.SuppressFinalize(this); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -168,8 +168,8 @@ public class CompositeConfigProvider : IConfigProvider | |||||||
|     #endregion |     #endregion | ||||||
|  |  | ||||||
|     #region 绑定 |     #region 绑定 | ||||||
|     private readonly ConcurrentDictionary<Object, String> _models = []; |     private readonly NonBlockingDictionary<Object, String> _models = []; | ||||||
|     private readonly ConcurrentDictionary<Object, ModelWrap> _models2 = []; |     private readonly NonBlockingDictionary<Object, ModelWrap> _models2 = []; | ||||||
|     /// <summary>绑定模型,使能热更新,配置存储数据改变时同步修改模型属性</summary> |     /// <summary>绑定模型,使能热更新,配置存储数据改变时同步修改模型属性</summary> | ||||||
|     /// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam> |     /// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam> | ||||||
|     /// <param name="model">模型实例</param> |     /// <param name="model">模型实例</param> | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ public static class DictionaryExtensions | |||||||
|     /// <param name="dict"></param> |     /// <param name="dict"></param> | ||||||
|     /// <param name="key"></param> |     /// <param name="key"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static Boolean Remove<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dict, TKey key) where TKey : notnull => dict.TryRemove(key, out _); |     public static Boolean Remove<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dict, TKey key) where TKey : notnull => dict.TryRemove(key, out _); | ||||||
|  |  | ||||||
| #if !NET6_0_OR_GREATER | #if !NET6_0_OR_GREATER | ||||||
|     public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> pairs, TKey key, TValue value) |     public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> pairs, TKey key, TValue value) | ||||||
| @@ -77,7 +77,7 @@ public static class DictionaryExtensions | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 批量出队 |     /// 批量出队 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public static List<T> ToListWithDequeue<TKEY, T>(this ConcurrentDictionary<TKEY, T> values, int maxCount = 0) |     public static List<T> ToListWithDequeue<TKEY, T>(this NonBlockingDictionary<TKEY, T> values, int maxCount = 0) | ||||||
|     { |     { | ||||||
|         if (maxCount <= 0) |         if (maxCount <= 0) | ||||||
|         { |         { | ||||||
| @@ -105,7 +105,7 @@ public static class DictionaryExtensions | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 批量出队 |     /// 批量出队 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public static Dictionary<TKEY, T> ToDictWithDequeue<TKEY, T>(this ConcurrentDictionary<TKEY, T> values, int maxCount = 0) |     public static Dictionary<TKEY, T> ToDictWithDequeue<TKEY, T>(this NonBlockingDictionary<TKEY, T> values, int maxCount = 0) | ||||||
|     { |     { | ||||||
|         if (maxCount <= 0) |         if (maxCount <= 0) | ||||||
|         { |         { | ||||||
| @@ -135,7 +135,7 @@ public static class DictionaryExtensions | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 批量出队 |     /// 批量出队 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public static IEnumerable<KeyValuePair<TKEY, T>> ToIEnumerableKVWithDequeue<TKEY, T>(this ConcurrentDictionary<TKEY, T> values, int maxCount = 0) |     public static IEnumerable<KeyValuePair<TKEY, T>> ToIEnumerableKVWithDequeue<TKEY, T>(this NonBlockingDictionary<TKEY, T> values, int maxCount = 0) | ||||||
|     { |     { | ||||||
|         if (values.IsEmpty) yield break; |         if (values.IsEmpty) yield break; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -101,7 +101,7 @@ public static class LinqExtensions | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 从并发字典中删除 |     /// 从并发字典中删除 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public static bool Remove<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dict, TKey key) where TKey : notnull |     public static bool Remove<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dict, TKey key) where TKey : notnull | ||||||
|     { |     { | ||||||
|         return dict.TryRemove(key, out TValue? _); |         return dict.TryRemove(key, out TValue? _); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -8,8 +8,6 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| using ThingsGateway.NewLife; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway; | namespace ThingsGateway; | ||||||
|  |  | ||||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||||
|   | |||||||
| @@ -97,7 +97,7 @@ public class ConsoleLog : Logger | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     static readonly ConcurrentDictionary<Int32, ConsoleColor> dic = new(); |     static readonly NonBlockingDictionary<Int32, ConsoleColor> dic = new(); | ||||||
|     static readonly ConsoleColor[] colors = [ |     static readonly ConsoleColor[] colors = [ | ||||||
|         ConsoleColor.Green, ConsoleColor.Cyan, ConsoleColor.Magenta, ConsoleColor.White, ConsoleColor.Yellow, |         ConsoleColor.Green, ConsoleColor.Cyan, ConsoleColor.Magenta, ConsoleColor.White, ConsoleColor.Yellow, | ||||||
|         ConsoleColor.DarkGreen, ConsoleColor.DarkCyan, ConsoleColor.DarkMagenta, ConsoleColor.DarkRed, ConsoleColor.DarkYellow ]; |         ConsoleColor.DarkGreen, ConsoleColor.DarkCyan, ConsoleColor.DarkMagenta, ConsoleColor.DarkRed, ConsoleColor.DarkYellow ]; | ||||||
|   | |||||||
| @@ -109,7 +109,7 @@ public class DefaultTracer : DisposeBase, ITracer, ILogFeature | |||||||
|     public IPool<ISpan> SpanPool => _SpanPool ??= new MySpanPool(); |     public IPool<ISpan> SpanPool => _SpanPool ??= new MySpanPool(); | ||||||
|  |  | ||||||
|     /// <summary>Span构建器集合</summary> |     /// <summary>Span构建器集合</summary> | ||||||
|     protected ConcurrentDictionary<String, ISpanBuilder> _builders = new(); |     protected NonBlockingDictionary<String, ISpanBuilder> _builders = new(); | ||||||
|  |  | ||||||
|     /// <summary>采样定时器</summary> |     /// <summary>采样定时器</summary> | ||||||
|     protected TimerX? _timer; |     protected TimerX? _timer; | ||||||
| @@ -292,7 +292,7 @@ public class DefaultTracer : DisposeBase, ITracer, ILogFeature | |||||||
|         var bs = _builders; |         var bs = _builders; | ||||||
|         if (bs.IsEmpty) return []; |         if (bs.IsEmpty) return []; | ||||||
|  |  | ||||||
|         _builders = new ConcurrentDictionary<String, ISpanBuilder>(); |         _builders = new NonBlockingDictionary<String, ISpanBuilder>(); | ||||||
|  |  | ||||||
|         var bs2 = bs.Values.Where(e => e.Total > 0).ToArray(); |         var bs2 = bs.Values.Where(e => e.Total > 0).ToArray(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -61,10 +61,11 @@ public class TextFileLog : Logger, IDisposable | |||||||
|         MaxBytes = set.LogFileMaxBytes; |         MaxBytes = set.LogFileMaxBytes; | ||||||
|         Backups = set.LogFileBackups; |         Backups = set.LogFileBackups; | ||||||
|  |  | ||||||
|         _Timer = new TimerX(DoWriteAndClose, null, 0_000, 5_000) { Async = true }; |         _Timer = new TimerX(DoWriteAndClose, null, 0_000, 60_000, nameof(TextFileLog)) { Async = true }; | ||||||
|  |         _WriteTimer = new TimerX(DoWrite, null, 0_000, 1000, nameof(TextFileLog)) { Async = true }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static readonly ConcurrentDictionary<String, TextFileLog> cache = new(StringComparer.OrdinalIgnoreCase); |     private static readonly NonBlockingDictionary<String, TextFileLog> cache = new(StringComparer.OrdinalIgnoreCase); | ||||||
|     /// <summary>每个目录的日志实例应该只有一个,所以采用静态创建</summary> |     /// <summary>每个目录的日志实例应该只有一个,所以采用静态创建</summary> | ||||||
|     /// <param name="path">日志目录或日志文件路径</param> |     /// <param name="path">日志目录或日志文件路径</param> | ||||||
|     /// <param name="fileFormat"></param> |     /// <param name="fileFormat"></param> | ||||||
| @@ -96,6 +97,7 @@ public class TextFileLog : Logger, IDisposable | |||||||
|     protected virtual void Dispose(Boolean disposing) |     protected virtual void Dispose(Boolean disposing) | ||||||
|     { |     { | ||||||
|         _Timer.TryDispose(); |         _Timer.TryDispose(); | ||||||
|  |         _WriteTimer.TryDispose(); | ||||||
|  |  | ||||||
|         // 销毁前把队列日志输出 |         // 销毁前把队列日志输出 | ||||||
|         if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0) WriteAndClose(DateTime.MinValue); |         if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0) WriteAndClose(DateTime.MinValue); | ||||||
| @@ -147,35 +149,72 @@ public class TextFileLog : Logger, IDisposable | |||||||
|  |  | ||||||
|     /// <summary>获取日志文件路径</summary> |     /// <summary>获取日志文件路径</summary> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     private String? GetLogFile() |     private string? GetLogFile() | ||||||
|     { |     { | ||||||
|         // 单日志文件 |         // 单日志文件 | ||||||
|         if (_isFile) return LogPath.GetBasePath(); |         if (_isFile) return LogPath.GetBasePath(); | ||||||
|  |  | ||||||
|         // 目录多日志文件 |         // 目录多日志文件 | ||||||
|         var logfile = LogPath.CombinePath(String.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level)).GetBasePath(); |         var baseFile = LogPath.CombinePath( | ||||||
|  |             string.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level) | ||||||
|  |         ).GetBasePath(); | ||||||
|  |  | ||||||
|         // 是否限制文件大小 |         // 不限制大小 | ||||||
|         if (MaxBytes == 0) return logfile; |         if (MaxBytes == 0) return baseFile; | ||||||
|  |  | ||||||
|         // 找到今天第一个未达到最大上限的文件 |  | ||||||
|         var max = MaxBytes * 1024L * 1024L; |         var max = MaxBytes * 1024L * 1024L; | ||||||
|         var ext = Path.GetExtension(logfile); |         var ext = Path.GetExtension(FileFormat); | ||||||
|         var name = logfile.TrimEnd(ext); |  | ||||||
|  |         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++) |         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; |         return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #endregion |     #endregion | ||||||
|  |  | ||||||
|     #region 异步写日志 |     #region 异步写日志 | ||||||
|     private readonly TimerX? _Timer; |     private readonly TimerX? _Timer; | ||||||
|  |     private readonly TimerX? _WriteTimer; | ||||||
|     private readonly ConcurrentQueue<String> _Logs = new(); |     private readonly ConcurrentQueue<String> _Logs = new(); | ||||||
|     private volatile Int32 _logCount; |     private volatile Int32 _logCount; | ||||||
|     private Int32 _writing; |     private Int32 _writing; | ||||||
| @@ -186,9 +225,9 @@ public class TextFileLog : Logger, IDisposable | |||||||
|     { |     { | ||||||
|         var writer = LogWriter; |         var writer = LogWriter; | ||||||
|  |  | ||||||
|         var now = TimerX.Now.AddHours(Setting.Current.UtcIntervalHours); |  | ||||||
|         var logFile = GetLogFile(); |         var logFile = GetLogFile(); | ||||||
|         if (logFile.IsNullOrEmpty()) return; |         if (logFile.IsNullOrEmpty()) return; | ||||||
|  |         var now = TimerX.Now.AddHours(Setting.Current.UtcIntervalHours); | ||||||
|  |  | ||||||
|         if (!_isFile && logFile != CurrentLogFile) |         if (!_isFile && logFile != CurrentLogFile) | ||||||
|         { |         { | ||||||
| @@ -223,7 +262,10 @@ public class TextFileLog : Logger, IDisposable | |||||||
|         // 连续5秒没日志,就关闭 |         // 连续5秒没日志,就关闭 | ||||||
|         _NextClose = now.AddSeconds(5); |         _NextClose = now.AddSeconds(5); | ||||||
|     } |     } | ||||||
|  |     private void DoWrite(Object? state) | ||||||
|  |     { | ||||||
|  |         WriteLog(); | ||||||
|  |     } | ||||||
|     /// <summary>关闭文件</summary> |     /// <summary>关闭文件</summary> | ||||||
|     private void DoWriteAndClose(Object? state) |     private void DoWriteAndClose(Object? state) | ||||||
|     { |     { | ||||||
| @@ -234,46 +276,39 @@ public class TextFileLog : Logger, IDisposable | |||||||
|         if (!_isFile && Backups > 0) |         if (!_isFile && Backups > 0) | ||||||
|         { |         { | ||||||
|             // 判断日志目录是否已存在 |             // 判断日志目录是否已存在 | ||||||
|             var di = LogPath.GetBasePath().AsDirectory(); |             DirectoryInfo? di = new DirectoryInfo(LogPath); | ||||||
|             if (di.Exists) |             if (di.Exists) | ||||||
|             { |             { | ||||||
|                 // 删除*.del |                 // 删除 *.del | ||||||
|                 try |                 try | ||||||
|                 { |                 { | ||||||
|                     var dels = di.GetFiles("*.del"); |                     foreach (var item in di.EnumerateFiles("*.del")) | ||||||
|                     if (dels?.Length > 0) |  | ||||||
|                     { |                     { | ||||||
|                         foreach (var item in dels) |                         item.Delete(); | ||||||
|                         { |  | ||||||
|                             item.Delete(); |  | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 catch { } |                 catch { } | ||||||
|  |  | ||||||
|                 var ext = Path.GetExtension(FileFormat); |                 var ext = Path.GetExtension(FileFormat); | ||||||
|                 var fis = di.GetFiles("*" + ext); |                 var fis = di.EnumerateFiles($"*{ext}") | ||||||
|                 if (fis != null && fis.Length > Backups) |                            .OrderByDescending(e => e.LastWriteTimeUtc) | ||||||
|  |                            .Skip(Backups); | ||||||
|  |  | ||||||
|  |                 foreach (var item in fis) | ||||||
|                 { |                 { | ||||||
|                     // 删除最旧的文件 |                     OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length); | ||||||
|                     var retain = fis.Length - Backups; |                     try | ||||||
|                     fis = fis.OrderBy(e => e.CreationTime).Take(retain).ToArray(); |                     { | ||||||
|                     foreach (var item in fis) |                         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 |                         try | ||||||
|                         { |                         { | ||||||
|                             item.Delete(); |                             item.MoveTo(item.FullName + ".del"); | ||||||
|                         } |                         } | ||||||
|                         catch |                         catch | ||||||
|                         { |                         { | ||||||
|                             try |  | ||||||
|                             { |  | ||||||
|                                 item.MoveTo(item.FullName + ".del"); |  | ||||||
|                             } |  | ||||||
|                             catch |  | ||||||
|                             { |  | ||||||
|                             } |  | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| @@ -323,7 +358,6 @@ public class TextFileLog : Logger, IDisposable | |||||||
|         // 推入队列 |         // 推入队列 | ||||||
|         Enqueue($"{e.GetAndReset()}"); |         Enqueue($"{e.GetAndReset()}"); | ||||||
|  |  | ||||||
|         WriteLog(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected bool Check() |     protected bool Check() | ||||||
| @@ -340,35 +374,17 @@ public class TextFileLog : Logger, IDisposable | |||||||
|     } |     } | ||||||
|     protected void WriteLog() |     protected void WriteLog() | ||||||
|     { |     { | ||||||
|         // 异步写日志,实时。即使这里错误,定时器那边仍然会补上 |         // 写日志,实时。即使这里错误,定时器那边仍然会补上 | ||||||
|         if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0) |         if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0) | ||||||
|         { |         { | ||||||
|             // 调试级别 或 致命错误 同步写日志 |             // 调试级别 或 致命错误 同步写日志 | ||||||
|             if (Setting.Current.LogLevel <= LogLevel.Debug || Level >= LogLevel.Error) |             try | ||||||
|             { |             { | ||||||
|                 try |                 if (!_Logs.IsEmpty) WriteFile(); | ||||||
|                 { |  | ||||||
|                     WriteFile(); |  | ||||||
|                 } |  | ||||||
|                 finally |  | ||||||
|                 { |  | ||||||
|                     _writing = 0; |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|             else |             finally | ||||||
|             { |             { | ||||||
|                 ThreadPool.UnsafeQueueUserWorkItem(s => |                 _writing = 0; | ||||||
|                 { |  | ||||||
|                     try |  | ||||||
|                     { |  | ||||||
|                         WriteFile(); |  | ||||||
|                     } |  | ||||||
|                     catch { } |  | ||||||
|                     finally |  | ||||||
|                     { |  | ||||||
|                         _writing = 0; |  | ||||||
|                     } |  | ||||||
|                 }, null); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -80,7 +80,14 @@ public static class XTrace | |||||||
|  |  | ||||||
|         Log.Error("{0}", ex); |         Log.Error("{0}", ex); | ||||||
|     } |     } | ||||||
|  |     public static void WriteException(Exception ex, string message) | ||||||
|  |     { | ||||||
|  |         if (!InitLog()) return; | ||||||
|  |  | ||||||
|  |         WriteVersion(); | ||||||
|  |  | ||||||
|  |         Log.Error("{0}, {1}", message, ex); | ||||||
|  |     } | ||||||
|     #endregion 写日志 |     #endregion 写日志 | ||||||
|  |  | ||||||
|     #region 构造 |     #region 构造 | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ public interface IEventHandler<TEvent> | |||||||
| /// </remarks> | /// </remarks> | ||||||
| public class EventBus<TEvent> : DisposeBase, IEventBus<TEvent> | public class EventBus<TEvent> : DisposeBase, IEventBus<TEvent> | ||||||
| { | { | ||||||
|     private readonly ConcurrentDictionary<String, IEventHandler<TEvent>> _handlers = []; |     private readonly NonBlockingDictionary<String, IEventHandler<TEvent>> _handlers = []; | ||||||
|     /// <summary>已订阅的事件处理器集合</summary> |     /// <summary>已订阅的事件处理器集合</summary> | ||||||
|     public IDictionary<String, IEventHandler<TEvent>> Handlers => _handlers; |     public IDictionary<String, IEventHandler<TEvent>> Handlers => _handlers; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,9 +20,9 @@ public class DeferredQueue : DisposeBase | |||||||
|     /// <summary>名称</summary> |     /// <summary>名称</summary> | ||||||
|     public String Name { get; set; } |     public String Name { get; set; } | ||||||
|  |  | ||||||
|     private volatile ConcurrentDictionary<String, Object> _Entities = new(); |     private volatile NonBlockingDictionary<String, Object> _Entities = new(); | ||||||
|     /// <summary>实体字典</summary> |     /// <summary>实体字典</summary> | ||||||
|     public ConcurrentDictionary<String, Object> Entities => _Entities; |     public NonBlockingDictionary<String, Object> Entities => _Entities; | ||||||
|  |  | ||||||
|     /// <summary>跟踪数。达到该值时输出跟踪日志,默认1000</summary> |     /// <summary>跟踪数。达到该值时输出跟踪日志,默认1000</summary> | ||||||
|     public Int32 TraceCount { get; set; } = 1000; |     public Int32 TraceCount { get; set; } = 1000; | ||||||
| @@ -206,7 +206,7 @@ public class DeferredQueue : DisposeBase | |||||||
|         var es = _Entities; |         var es = _Entities; | ||||||
|         if (es.IsEmpty) return; |         if (es.IsEmpty) return; | ||||||
|  |  | ||||||
|         _Entities = new ConcurrentDictionary<String, Object>(); |         _Entities = new NonBlockingDictionary<String, Object>(); | ||||||
|         var times = _Times; |         var times = _Times; | ||||||
|  |  | ||||||
|         Interlocked.Add(ref _count, -es.Count); |         Interlocked.Add(ref _count, -es.Count); | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ class MyServiceScope : IServiceScope, IServiceProvider | |||||||
|  |  | ||||||
|     public IServiceProvider ServiceProvider => this; |     public IServiceProvider ServiceProvider => this; | ||||||
|  |  | ||||||
|     private readonly ConcurrentDictionary<Type, Object?> _cache = new(); |     private readonly NonBlockingDictionary<Type, Object?> _cache = new(); | ||||||
|  |  | ||||||
|     public void Dispose() |     public void Dispose() | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ public class DnsResolver : IDnsResolver | |||||||
|     /// <summary>缓存超时时间</summary> |     /// <summary>缓存超时时间</summary> | ||||||
|     public TimeSpan Expire { set; get; } = TimeSpan.FromMinutes(5); |     public TimeSpan Expire { set; get; } = TimeSpan.FromMinutes(5); | ||||||
|  |  | ||||||
|     private readonly ConcurrentDictionary<String, DnsItem> _cache = new(); |     private readonly NonBlockingDictionary<String, DnsItem> _cache = new(); | ||||||
|  |  | ||||||
|     /// <summary>解析域名</summary> |     /// <summary>解析域名</summary> | ||||||
|     /// <param name="host"></param> |     /// <param name="host"></param> | ||||||
|   | |||||||
| @@ -146,7 +146,7 @@ public class NetServer : DisposeBase, IServer, IExtend, ILogFeature | |||||||
|     /// </remarks> |     /// </remarks> | ||||||
|     public IServiceProvider? ServiceProvider { get; set; } |     public IServiceProvider? ServiceProvider { get; set; } | ||||||
|  |  | ||||||
|     private ConcurrentDictionary<String, Object?>? _items; |     private NonBlockingDictionary<String, Object?>? _items; | ||||||
|     /// <summary>数据项</summary> |     /// <summary>数据项</summary> | ||||||
|     public IDictionary<String, Object?> Items => _items ??= new(); |     public IDictionary<String, Object?> Items => _items ??= new(); | ||||||
|  |  | ||||||
| @@ -486,7 +486,7 @@ public class NetServer : DisposeBase, IServer, IExtend, ILogFeature | |||||||
|     #endregion |     #endregion | ||||||
|  |  | ||||||
|     #region 会话 |     #region 会话 | ||||||
|     private readonly ConcurrentDictionary<Int32, INetSession> _Sessions = new(); |     private readonly NonBlockingDictionary<Int32, INetSession> _Sessions = new(); | ||||||
|     /// <summary>会话集合。用自增的数字ID作为标识,业务应用自己维持ID与业务主键的对应关系。</summary> |     /// <summary>会话集合。用自增的数字ID作为标识,业务应用自己维持ID与业务主键的对应关系。</summary> | ||||||
|     public IDictionary<Int32, INetSession> Sessions => _Sessions; |     public IDictionary<Int32, INetSession> Sessions => _Sessions; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -853,7 +853,7 @@ public abstract class SessionBase : DisposeBase, ISocketClient, ITransport, ILog | |||||||
|     #endregion 异常处理 |     #endregion 异常处理 | ||||||
|  |  | ||||||
|     #region 扩展接口 |     #region 扩展接口 | ||||||
|     private ConcurrentDictionary<String, Object?>? _items; |     private NonBlockingDictionary<String, Object?>? _items; | ||||||
|     /// <summary>数据项</summary> |     /// <summary>数据项</summary> | ||||||
|     public IDictionary<String, Object?> Items => _items ??= new(); |     public IDictionary<String, Object?> Items => _items ??= new(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ namespace ThingsGateway.NewLife.Net; | |||||||
| internal class SessionCollection : DisposeBase, IDictionary<String, ISocketSession> | internal class SessionCollection : DisposeBase, IDictionary<String, ISocketSession> | ||||||
| { | { | ||||||
|     #region 属性 |     #region 属性 | ||||||
|     private readonly ConcurrentDictionary<String, ISocketSession> _dic = new(); |     private readonly NonBlockingDictionary<String, ISocketSession> _dic = new(); | ||||||
|  |  | ||||||
|     /// <summary>服务端</summary> |     /// <summary>服务端</summary> | ||||||
|     public ISocketServer Server { get; private set; } |     public ISocketServer Server { get; private set; } | ||||||
|   | |||||||
| @@ -400,7 +400,7 @@ public class UdpSession : DisposeBase, ISocketSession, ITransport, ILogFeature | |||||||
|     #endregion |     #endregion | ||||||
|  |  | ||||||
|     #region 扩展接口 |     #region 扩展接口 | ||||||
|     private ConcurrentDictionary<String, Object?>? _items; |     private NonBlockingDictionary<String, Object?>? _items; | ||||||
|     /// <summary>数据项</summary> |     /// <summary>数据项</summary> | ||||||
|     public IDictionary<String, Object?> Items => _items ??= new(); |     public IDictionary<String, Object?> Items => _items ??= new(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  |  | ||||||
|  | namespace PooledAwait | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Provides async/await-related extension methods | ||||||
|  |     /// </summary> | ||||||
|  |     public static class AwaitableExtensions | ||||||
|  |     { | ||||||
|  |         /// <summary>Controls whether a yield operation should respect captured context</summary> | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         public static ConfiguredYieldAwaitable ConfigureAwait(this YieldAwaitable _, bool continueOnCapturedContext) | ||||||
|  |             => new ConfiguredYieldAwaitable(continueOnCapturedContext); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,125 @@ | |||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  |  | ||||||
|  | namespace PooledAwait | ||||||
|  | { | ||||||
|  |     /// <summary>Provides an awaitable context for switching into a target environment.</summary> | ||||||
|  |     public readonly struct ConfiguredYieldAwaitable | ||||||
|  |     { | ||||||
|  |         /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||||
|  |         public override bool Equals(object? obj) => obj is ConfiguredYieldAwaitable other && other._continueOnCapturedContext == _continueOnCapturedContext; | ||||||
|  |         /// <summary><see cref="Object.GetHashCode"/></summary> | ||||||
|  |         public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0; | ||||||
|  |         /// <summary><see cref="Object.ToString"/></summary> | ||||||
|  |         public override string ToString() => nameof(ConfiguredYieldAwaitable); | ||||||
|  |  | ||||||
|  |         private readonly bool _continueOnCapturedContext; | ||||||
|  |  | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         internal ConfiguredYieldAwaitable(bool continueOnCapturedContext) | ||||||
|  |             => _continueOnCapturedContext = continueOnCapturedContext; | ||||||
|  |  | ||||||
|  |         /// <summary>Gets an awaiter for this <see cref="ConfiguredYieldAwaitable"/>.</summary> | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         public ConfiguredYieldAwaiter GetAwaiter() | ||||||
|  |             => new ConfiguredYieldAwaiter(_continueOnCapturedContext); | ||||||
|  |  | ||||||
|  |         /// <summary>Provides an awaitable context for switching into a target environment.</summary> | ||||||
|  |         public readonly struct ConfiguredYieldAwaiter : ICriticalNotifyCompletion, INotifyCompletion | ||||||
|  |         { | ||||||
|  |             /// <summary><see cref="Object.Equals(Object)"/></summary> | ||||||
|  |             public override bool Equals(object? obj) => obj is ConfiguredYieldAwaiter other && other._continueOnCapturedContext == _continueOnCapturedContext; | ||||||
|  |             /// <summary><see cref="Object.GetHashCode"/></summary> | ||||||
|  |             public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0; | ||||||
|  |             /// <summary><see cref="Object.ToString"/></summary> | ||||||
|  |             public override string ToString() => nameof(ConfiguredYieldAwaiter); | ||||||
|  |  | ||||||
|  |             private readonly bool _continueOnCapturedContext; | ||||||
|  |  | ||||||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |             internal ConfiguredYieldAwaiter(bool continueOnCapturedContext) | ||||||
|  |                 => _continueOnCapturedContext = continueOnCapturedContext; | ||||||
|  |  | ||||||
|  |             /// <summary>Gets whether a yield is not required.</summary> | ||||||
|  |             public bool IsCompleted | ||||||
|  |             { | ||||||
|  |                 [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |                 get => false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             /// <summary>Ends the await operation.</summary> | ||||||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |             public void GetResult() { } | ||||||
|  |  | ||||||
|  |             /// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary> | ||||||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |             public void OnCompleted(Action continuation) | ||||||
|  |             { | ||||||
|  |                 if (_continueOnCapturedContext) YieldFlowContext(continuation, true); | ||||||
|  |                 else YieldNoContext(continuation, true); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             /// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary> | ||||||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |             public void UnsafeOnCompleted(Action continuation) | ||||||
|  |             { | ||||||
|  |                 if (_continueOnCapturedContext) YieldFlowContext(continuation, false); | ||||||
|  |                 else YieldNoContext(continuation, false); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             private static readonly WaitCallback s_waitCallbackRunAction = state => ((Action?)state)?.Invoke(); | ||||||
|  |  | ||||||
|  | #if PLAT_THREADPOOLWORKITEM | ||||||
|  |             private sealed class ContinuationWorkItem : IThreadPoolWorkItem | ||||||
|  |             { | ||||||
|  |                 private Action? _continuation; | ||||||
|  |  | ||||||
|  |                 [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |                 private ContinuationWorkItem() => Internal.Counters.ItemBoxAllocated.Increment(); | ||||||
|  |  | ||||||
|  |                 [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |                 public static ContinuationWorkItem Create(Action continuation) | ||||||
|  |                 { | ||||||
|  |                     var box = Pool<ContinuationWorkItem>.TryGet() ?? new ContinuationWorkItem(); | ||||||
|  |                     box._continuation = continuation; | ||||||
|  |                     return box; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |                 void IThreadPoolWorkItem.Execute() | ||||||
|  |                 { | ||||||
|  |                     var callback = _continuation; | ||||||
|  |                     _continuation = null; | ||||||
|  |                     Pool<ContinuationWorkItem>.TryPut(this); | ||||||
|  |                     callback?.Invoke(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | #endif | ||||||
|  |             [MethodImpl(MethodImplOptions.NoInlining)] // no-one ever calls ConfigureAwait(true)! | ||||||
|  |             private static void YieldFlowContext(Action continuation, bool flowContext) | ||||||
|  |             { | ||||||
|  |                 var awaiter = default(YieldAwaitable.YieldAwaiter); | ||||||
|  |                 if (flowContext) awaiter.OnCompleted(continuation); | ||||||
|  |                 else awaiter.UnsafeOnCompleted(continuation); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |             private static void YieldNoContext(Action continuation, bool flowContext) | ||||||
|  |             { | ||||||
|  |                 if (flowContext) | ||||||
|  |                 { | ||||||
|  |                     ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  | #if PLAT_THREADPOOLWORKITEM | ||||||
|  |                     ThreadPool.UnsafeQueueUserWorkItem(ContinuationWorkItem.Create(continuation), false); | ||||||
|  | #elif NETSTANDARD1_3 | ||||||
|  |                     ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation); | ||||||
|  | #else | ||||||
|  |                     ThreadPool.UnsafeQueueUserWorkItem(s_waitCallbackRunAction, continuation); | ||||||
|  | #endif | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user