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