From 6f9ec2e24bb1e4c4fbc9c1bfa4d1782ef102c051 Mon Sep 17 00:00:00 2001 From: "2248356998 qq.com" <2248356998@qq.com> Date: Wed, 15 Oct 2025 17:40:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B9=B6=E5=8F=91=E5=AD=97=E5=85=B8?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E4=B8=BA=20NonBlockingDictionary=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/Event/EventService.cs | 2 +- .../Extensions/ObjectExtensions.cs | 2 +- .../App/Extensions/ObjectExtensions.cs | 2 +- ...tCoreBuilderServiceCollectionExtensions.cs | 6 +- .../ModelBinders/Binders/FromConvertBinder.cs | 4 +- .../Binders/FromConvertBinderProvider.cs | 4 +- .../Validators/DataValidator.cs | 10 +- ...ncyInjectionServiceCollectionExtensions.cs | 4 +- .../Internal/Penetrates.cs | 12 +- .../HostedServices/EventBusHostedService.cs | 2 +- .../FriendlyException/Oops.cs | 10 +- .../Logging/Extensions/ILoggerExtensions.cs | 2 +- .../Extensions/StringLoggingExtensions.cs | 2 +- .../Implantations/File/FileLoggerProvider.cs | 2 +- .../Internal/StringLoggingPartSetters.cs | 2 +- src/Admin/ThingsGateway.Furion/Logging/Log.cs | 2 +- .../Cancellations/JobCancellationToken.cs | 2 +- .../Schedule/Details/JobDetail.Methods.cs | 2 +- .../Factories/SchedulerFactory.Internal.cs | 2 +- .../Schedule/Triggers/Trigger.Methods.cs | 2 +- .../Builders/SpecificationDocumentBuilder.cs | 20 +- .../UnifyResult/UnifyContext.cs | 4 +- .../ConcurrentDictionaryExtensions.cs | 6 +- .../Core/Extensions/IDictionaryExtensions.cs | 4 +- .../V5_Experience/Core/Options/CoreOptions.cs | 8 +- .../Core/Reflection/ObjectPropertyGetter.cs | 2 +- .../Core/Reflection/ObjectPropertySetter.cs | 2 +- .../Builders/HttpDeclarativeBuilder.cs | 4 +- .../Processors/MessagePackContentProcessor.cs | 2 +- .../Caching/MemoryCache.cs | 4 +- .../ThingsGateway.NewLife.X/Caching/Readme.MD | 2 +- .../Collections/CollectionHelper.cs | 6 +- .../Collections/ConcurrentHashSet.cs | 2 +- .../DictionaryImpl.SnapshotImpl.cs | 77 + .../ConcurrentDictionary/DictionaryImpl.cs | 93 ++ .../DictionaryImplBoxed.cs | 138 ++ .../ConcurrentDictionary/DictionaryImplInt.cs | 143 ++ .../DictionaryImplLong.cs | 143 ++ .../DictionaryImplNint.cs | 143 ++ .../ConcurrentDictionary/DictionaryImplRef.cs | 95 ++ .../ConcurrentDictionary/DictionaryImpl`2.cs | 81 + .../ConcurrentDictionary/DictionaryImpl`3.cs | 1453 ++++++++++++++++ .../Counter/Counter32.cs | 204 +++ .../Counter/Counter64.cs | 203 +++ .../Counter/CounterBase.cs | 38 + .../NonBlockingDictionary.cs | 1460 +++++++++++++++++ .../Collections/NonBlockingDictionary/Util.cs | 21 + .../Collections/ObjectPool.cs | 2 +- .../Common/ExpiringDictionary.cs | 5 +- .../Common/FastMapper.cs | 4 +- .../Configuration/CompositeConfigProvider.cs | 4 +- .../Extension/DictionaryExtensions.cs | 8 +- .../Extension/LinqExtensions.cs | 2 +- .../Logger/ConsoleLog.cs | 2 +- .../ThingsGateway.NewLife.X/Logger/ITracer.cs | 4 +- .../Logger/TextFileLog.cs | 4 +- .../Messaging/IEventBus.cs | 2 +- .../Model/DeferredQueue.cs | 6 +- .../Model/IServiceScope.cs | 2 +- .../Net/IDnsResolver.cs | 2 +- .../ThingsGateway.NewLife.X/Net/NetServer.cs | 4 +- .../Net/SessionBase.cs | 2 +- .../Net/SessionCollection.cs | 2 +- .../ThingsGateway.NewLife.X/Net/UdpSession.cs | 2 +- .../Redis/FullRedis.cs | 2 +- .../Redis/RedisClient.cs | 8 +- .../Reflection/AssemblyX.cs | 6 +- .../Reflection/AttributeX.cs | 2 +- .../Reflection/IReflect.cs | 12 +- .../Reflection/ScriptEngine.cs | 2 +- .../Serialization/Binary/BinaryComposite.cs | 2 +- .../Serialization/Json/IJsonHost.cs | 2 +- .../Serialization/SerialHelper.cs | 2 +- .../IntegrationServices/SerializeService.cs | 2 +- .../Sugar/Utilities/CallContextAsync.cs | 4 +- .../Sugar/Utilities/FastCopy.cs | 2 +- .../Utilities/PropertyCallAdapterProvider.cs | 4 +- src/Directory.Build.props | 6 +- .../Channel/DDP/DDPUdpSessionChannel.cs | 2 +- .../DDP/InternalConcurrentDictionary.cs | 2 +- .../DeviceSingleStreamDataHandleAdapter.cs | 4 +- .../Device/DeviceBase.cs | 12 +- .../Localization/JsonLocalizer.cs | 2 +- .../Driver/Collect/CollectBase.cs | 4 +- .../Driver/Collect/CollectFoundationBase.cs | 2 +- .../Entity/Variable.cs | 2 +- .../GlobalData/GlobalData.cs | 16 +- .../Model/ChannelRuntime.cs | 2 +- .../Model/DeviceRunTime.cs | 2 +- .../Services/Device/DeviceService.cs | 4 +- .../Services/Device/DeviceServiceHelpers.cs | 6 +- .../Services/Device/DeviceServiceHelpers.m.cs | 6 +- .../Services/Device/IDeviceService.cs | 2 +- .../ChannelManage/ChannelThreadManage.cs | 2 +- .../ChannelManage/IChannelThreadManage.cs | 2 +- .../DeviceManage/DeviceThreadManage.cs | 6 +- .../Services/Plugin/PluginService.cs | 6 +- .../Services/Plugin/PluginServiceUtil.cs | 2 +- .../Services/Rpc/RpcService.cs | 2 +- .../Trigger/AlarmChangedTriggerNode.cs | 4 +- .../Trigger/ValueChangedTriggerNode.cs | 4 +- .../Services/Variable/IVariableService.cs | 2 +- .../Services/Variable/VariableService.cs | 4 +- .../Variable/VariableServiceHelpers.cs | 14 +- .../Variable/VariableEditComponent.razor.cs | 4 +- .../Variable/VariableRow.razor.cs | 8 +- .../Master/Core/ModbusTcpMessage.cs | 91 +- .../Master/ModbusMaster.cs | 7 +- .../Slave/ModbusSlave.cs | 8 +- .../ThingsGateway.Foundation.OpcDa.csproj | 1 + .../ThingsGateway.Foundation.OpcUa.csproj | 2 +- .../S7/SiemensS7Master.cs | 11 +- .../SqlDB/SqlDbProducer.other.cs | 2 +- .../ModbusSlave/ModbusSlave.cs | 2 +- .../OpcDaMaster/OpcDaMaster.cs | 2 +- .../OpcUaMaster/OpcUaMaster.cs | 2 +- .../OpcUaServer/OpcUaServer.cs | 2 +- .../SiemensS7Master/SiemensS7Master.cs | 2 +- .../Test/TestKafkaDynamicModel.cs | 2 +- 119 files changed, 4564 insertions(+), 245 deletions(-) create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl.SnapshotImpl.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplBoxed.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplInt.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplLong.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplNint.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplRef.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl`2.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl`3.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Counter/Counter32.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Counter/Counter64.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Counter/CounterBase.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/NonBlockingDictionary.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Util.cs diff --git a/src/Admin/ThingsGateway.Admin.Application/Services/Event/EventService.cs b/src/Admin/ThingsGateway.Admin.Application/Services/Event/EventService.cs index d5a89f7bf..26b7d14e0 100644 --- a/src/Admin/ThingsGateway.Admin.Application/Services/Event/EventService.cs +++ b/src/Admin/ThingsGateway.Admin.Application/Services/Event/EventService.cs @@ -20,7 +20,7 @@ namespace ThingsGateway.Admin.Application; /// public class EventService : IEventService, IDisposable { - private ConcurrentDictionary> Cache = new(); + private NonBlockingDictionary> Cache = new(); public void Dispose() { diff --git a/src/Admin/ThingsGateway.Common/Extensions/ObjectExtensions.cs b/src/Admin/ThingsGateway.Common/Extensions/ObjectExtensions.cs index f3c496fd7..d3404fb55 100644 --- a/src/Admin/ThingsGateway.Common/Extensions/ObjectExtensions.cs +++ b/src/Admin/ThingsGateway.Common/Extensions/ObjectExtensions.cs @@ -82,7 +82,7 @@ public static class ObjectExtensions /// /// 字典 /// 新字典 - internal static void AddOrUpdate(this ConcurrentDictionary dic, Dictionary newDic) + internal static void AddOrUpdate(this NonBlockingDictionary dic, Dictionary newDic) { foreach (var (key, value) in newDic) { diff --git a/src/Admin/ThingsGateway.Furion/App/Extensions/ObjectExtensions.cs b/src/Admin/ThingsGateway.Furion/App/Extensions/ObjectExtensions.cs index cdf2150e3..80fe42d56 100644 --- a/src/Admin/ThingsGateway.Furion/App/Extensions/ObjectExtensions.cs +++ b/src/Admin/ThingsGateway.Furion/App/Extensions/ObjectExtensions.cs @@ -205,7 +205,7 @@ public static class ObjectExtensions /// /// 字典 /// 新字典 - internal static void AddOrUpdate(this ConcurrentDictionary dic, Dictionary newDic) + internal static void AddOrUpdate(this NonBlockingDictionary dic, Dictionary newDic) { foreach (var (key, value) in newDic) { diff --git a/src/Admin/ThingsGateway.Furion/AspNetCore/Extensions/AspNetCoreBuilderServiceCollectionExtensions.cs b/src/Admin/ThingsGateway.Furion/AspNetCore/Extensions/AspNetCoreBuilderServiceCollectionExtensions.cs index 66eeb2537..149f65443 100644 --- a/src/Admin/ThingsGateway.Furion/AspNetCore/Extensions/AspNetCoreBuilderServiceCollectionExtensions.cs +++ b/src/Admin/ThingsGateway.Furion/AspNetCore/Extensions/AspNetCoreBuilderServiceCollectionExtensions.cs @@ -94,7 +94,7 @@ public static class AspNetCoreBuilderServiceCollectionExtensions /// /// /// - public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action> configure = default) + public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action> configure = default) { mvcBuilder.Services.AddFromConvertBinding(configure); @@ -107,13 +107,13 @@ public static class AspNetCoreBuilderServiceCollectionExtensions /// /// /// - public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action> configure = default) + public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action> configure = default) { // 非 Web 环境跳过注册 if (App.WebHostEnvironment == default) return services; // 定义模型绑定转换器集合 - var modelBinderConverts = new ConcurrentDictionary(); + var modelBinderConverts = new NonBlockingDictionary(); modelBinderConverts.TryAdd(typeof(DateTime), typeof(DateTimeModelConvertBinder)); modelBinderConverts.TryAdd(typeof(DateTimeOffset), typeof(DateTimeOffsetModelConvertBinder)); diff --git a/src/Admin/ThingsGateway.Furion/AspNetCore/ModelBinders/Binders/FromConvertBinder.cs b/src/Admin/ThingsGateway.Furion/AspNetCore/ModelBinders/Binders/FromConvertBinder.cs index 818382e7a..2eaceeb58 100644 --- a/src/Admin/ThingsGateway.Furion/AspNetCore/ModelBinders/Binders/FromConvertBinder.cs +++ b/src/Admin/ThingsGateway.Furion/AspNetCore/ModelBinders/Binders/FromConvertBinder.cs @@ -27,13 +27,13 @@ public class FromConvertBinder : IModelBinder /// /// 定义模型绑定转换器集合 /// - private readonly ConcurrentDictionary _modelBinderConverts; + private readonly NonBlockingDictionary _modelBinderConverts; /// /// 构造函数 /// /// 定义模型绑定转换器集合 - public FromConvertBinder(ConcurrentDictionary modelBinderConverts) + public FromConvertBinder(NonBlockingDictionary modelBinderConverts) { _modelBinderConverts = modelBinderConverts; } diff --git a/src/Admin/ThingsGateway.Furion/AspNetCore/ModelBinders/Binders/FromConvertBinderProvider.cs b/src/Admin/ThingsGateway.Furion/AspNetCore/ModelBinders/Binders/FromConvertBinderProvider.cs index 409946fdb..fd376f1d0 100644 --- a/src/Admin/ThingsGateway.Furion/AspNetCore/ModelBinders/Binders/FromConvertBinderProvider.cs +++ b/src/Admin/ThingsGateway.Furion/AspNetCore/ModelBinders/Binders/FromConvertBinderProvider.cs @@ -28,13 +28,13 @@ public class FromConvertBinderProvider : IModelBinderProvider /// /// 定义模型绑定转换器集合 /// - private readonly ConcurrentDictionary _modelBinderConverts; + private readonly NonBlockingDictionary _modelBinderConverts; /// /// 构造函数 /// /// 定义模型绑定转换器集合 - public FromConvertBinderProvider(ConcurrentDictionary modelBinderConverts) + public FromConvertBinderProvider(NonBlockingDictionary modelBinderConverts) { _modelBinderConverts = modelBinderConverts; } diff --git a/src/Admin/ThingsGateway.Furion/DataValidation/Validators/DataValidator.cs b/src/Admin/ThingsGateway.Furion/DataValidation/Validators/DataValidator.cs index 6d0432316..290ef9011 100644 --- a/src/Admin/ThingsGateway.Furion/DataValidation/Validators/DataValidator.cs +++ b/src/Admin/ThingsGateway.Furion/DataValidation/Validators/DataValidator.cs @@ -40,7 +40,7 @@ public static class DataValidator /// /// 验证类型正则表达式 /// - private static readonly ConcurrentDictionary ValidationItemMetadatas; + private static readonly NonBlockingDictionary ValidationItemMetadatas; /// /// 构造函数 @@ -57,7 +57,7 @@ public static class DataValidator ValidationItemMetadatas = GetValidationValidationItemMetadatas(); // 缓存所有正则表达式 - GetValidationTypeValidationItemMetadataCached = new ConcurrentDictionary(); + GetValidationTypeValidationItemMetadataCached = new NonBlockingDictionary(); } /// @@ -203,7 +203,7 @@ public static class DataValidator /// /// 获取验证类型验证Item集合 /// - private static readonly ConcurrentDictionary GetValidationTypeValidationItemMetadataCached; + private static readonly NonBlockingDictionary GetValidationTypeValidationItemMetadataCached; /// /// 获取验证类型正则表达式(需要缓存) @@ -267,9 +267,9 @@ public static class DataValidator /// 获取验证类型所有有效的正则表达式 /// /// - private static ConcurrentDictionary GetValidationValidationItemMetadatas() + private static NonBlockingDictionary GetValidationValidationItemMetadatas() { - var vaidationItems = new ConcurrentDictionary(); + var vaidationItems = new NonBlockingDictionary(); // 查找所有 [ValidationMessageType] 类型中的 [ValidationMessage] 消息定义 var customErrorMessages = ValidationMessageTypes.SelectMany(u => u.GetFields() diff --git a/src/Admin/ThingsGateway.Furion/DependencyInjection/Extensions/DependencyInjectionServiceCollectionExtensions.cs b/src/Admin/ThingsGateway.Furion/DependencyInjection/Extensions/DependencyInjectionServiceCollectionExtensions.cs index 45e76af77..dd7ab9b0a 100644 --- a/src/Admin/ThingsGateway.Furion/DependencyInjection/Extensions/DependencyInjectionServiceCollectionExtensions.cs +++ b/src/Admin/ThingsGateway.Furion/DependencyInjection/Extensions/DependencyInjectionServiceCollectionExtensions.cs @@ -353,7 +353,7 @@ public static class DependencyInjectionServiceCollectionExtensions /// /// 类型名称集合 /// - private static readonly ConcurrentDictionary TypeNamedCollection; + private static readonly NonBlockingDictionary TypeNamedCollection; /// /// 创建代理方法 @@ -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(); + TypeNamedCollection = new NonBlockingDictionary(); DispatchCreateMethod = typeof(AspectDispatchProxy).GetMethod(nameof(AspectDispatchProxy.Create)); } } diff --git a/src/Admin/ThingsGateway.Furion/DynamicApiController/Internal/Penetrates.cs b/src/Admin/ThingsGateway.Furion/DynamicApiController/Internal/Penetrates.cs index b19cd8a80..a69630c9c 100644 --- a/src/Admin/ThingsGateway.Furion/DynamicApiController/Internal/Penetrates.cs +++ b/src/Admin/ThingsGateway.Furion/DynamicApiController/Internal/Penetrates.cs @@ -28,21 +28,21 @@ internal static class Penetrates /// /// 请求动词映射字典 /// - internal static ConcurrentDictionary VerbToHttpMethods { get; private set; } + internal static NonBlockingDictionary VerbToHttpMethods { get; private set; } /// /// 控制器排序集合 /// - internal static ConcurrentDictionary ControllerOrderCollection { get; set; } + internal static NonBlockingDictionary ControllerOrderCollection { get; set; } /// /// 构造函数 /// static Penetrates() { - ControllerOrderCollection = new ConcurrentDictionary(); + ControllerOrderCollection = new NonBlockingDictionary(); - VerbToHttpMethods = new ConcurrentDictionary + VerbToHttpMethods = new NonBlockingDictionary { ["post"] = "POST", ["add"] = "POST", @@ -67,13 +67,13 @@ internal static class Penetrates ["patch"] = "PATCH" }; - //IsApiControllerCached = new ConcurrentDictionary(); + //IsApiControllerCached = new NonBlockingDictionary(); } ///// ///// 缓存集合 ///// - //private static readonly ConcurrentDictionary IsApiControllerCached; + //private static readonly NonBlockingDictionary IsApiControllerCached; /// /// 是否是Api控制器 diff --git a/src/Admin/ThingsGateway.Furion/EventBus/HostedServices/EventBusHostedService.cs b/src/Admin/ThingsGateway.Furion/EventBus/HostedServices/EventBusHostedService.cs index 0c6ee8a05..0369a03e6 100644 --- a/src/Admin/ThingsGateway.Furion/EventBus/HostedServices/EventBusHostedService.cs +++ b/src/Admin/ThingsGateway.Furion/EventBus/HostedServices/EventBusHostedService.cs @@ -61,7 +61,7 @@ internal sealed class EventBusHostedService : BackgroundService /// /// 事件处理程序集合 /// - private readonly ConcurrentDictionary _eventHandlers = new(); + private readonly NonBlockingDictionary _eventHandlers = new(); /// /// 构造函数 diff --git a/src/Admin/ThingsGateway.Furion/FriendlyException/Oops.cs b/src/Admin/ThingsGateway.Furion/FriendlyException/Oops.cs index 5d7a1ee9f..e2f9e007d 100644 --- a/src/Admin/ThingsGateway.Furion/FriendlyException/Oops.cs +++ b/src/Admin/ThingsGateway.Furion/FriendlyException/Oops.cs @@ -31,7 +31,7 @@ public static class Oops /// /// 方法错误异常特性 /// - private static readonly ConcurrentDictionary _errorMethods; + private static readonly NonBlockingDictionary _errorMethods; /// /// 错误代码类型 @@ -41,7 +41,7 @@ public static class Oops /// /// 错误消息字典 /// - private static readonly ConcurrentDictionary _errorCodeMessages; + private static readonly NonBlockingDictionary _errorCodeMessages; /// /// 友好异常设置 @@ -53,7 +53,7 @@ public static class Oops /// static Oops() { - _errorMethods = new ConcurrentDictionary(); + _errorMethods = new NonBlockingDictionary(); _friendlyExceptionSettings = App.GetConfig("FriendlyExceptionSettings", true); _errorCodeTypes = GetErrorCodeTypes(); _errorCodeMessages = GetErrorCodeMessages(); @@ -258,9 +258,9 @@ public static class Oops /// 获取所有错误消息 /// /// - private static ConcurrentDictionary GetErrorCodeMessages() + private static NonBlockingDictionary GetErrorCodeMessages() { - var defaultErrorCodeMessages = new ConcurrentDictionary(); + var defaultErrorCodeMessages = new NonBlockingDictionary(); // 查找所有 [ErrorCodeType] 类型中的 [ErrorCodeMetadata] 元数据定义 var errorCodeMessages = _errorCodeTypes.SelectMany(u => u.GetFields().Where(u => u.IsDefined(typeof(ErrorCodeItemMetadataAttribute)))) diff --git a/src/Admin/ThingsGateway.Furion/Logging/Extensions/ILoggerExtensions.cs b/src/Admin/ThingsGateway.Furion/Logging/Extensions/ILoggerExtensions.cs index b639393c6..d66ae5014 100644 --- a/src/Admin/ThingsGateway.Furion/Logging/Extensions/ILoggerExtensions.cs +++ b/src/Admin/ThingsGateway.Furion/Logging/Extensions/ILoggerExtensions.cs @@ -23,7 +23,7 @@ public static class ILoggerExtensions /// 设置日志上下文 /// /// - /// 建议使用 ConcurrentDictionary 类型 + /// 建议使用 NonBlockingDictionary 类型 /// public static IDisposable ScopeContext(this ILogger logger, IDictionary properties) { diff --git a/src/Admin/ThingsGateway.Furion/Logging/Extensions/StringLoggingExtensions.cs b/src/Admin/ThingsGateway.Furion/Logging/Extensions/StringLoggingExtensions.cs index a8a761101..e43a029ea 100644 --- a/src/Admin/ThingsGateway.Furion/Logging/Extensions/StringLoggingExtensions.cs +++ b/src/Admin/ThingsGateway.Furion/Logging/Extensions/StringLoggingExtensions.cs @@ -82,7 +82,7 @@ public static class StringLoggingExtensions /// 配置日志上下文 /// /// - /// 建议使用 ConcurrentDictionary 类型 + /// 建议使用 NonBlockingDictionary 类型 /// public static StringLoggingPart ScopeContext(this string message, IDictionary properties) { diff --git a/src/Admin/ThingsGateway.Furion/Logging/Implantations/File/FileLoggerProvider.cs b/src/Admin/ThingsGateway.Furion/Logging/Implantations/File/FileLoggerProvider.cs index e21aa8399..996ccc7d8 100644 --- a/src/Admin/ThingsGateway.Furion/Logging/Implantations/File/FileLoggerProvider.cs +++ b/src/Admin/ThingsGateway.Furion/Logging/Implantations/File/FileLoggerProvider.cs @@ -44,7 +44,7 @@ public sealed class FileLoggerProvider : ILoggerProvider, ISupportExternalScope /// 记录日志所有滚动文件名 /// /// 只有 MaxRollingFiles 和 FileSizeLimitBytes 大于 0 有效 - internal readonly ConcurrentDictionary _rollingFileNames = new(); + internal readonly NonBlockingDictionary _rollingFileNames = new(); /// /// 文件日志写入器 diff --git a/src/Admin/ThingsGateway.Furion/Logging/Internal/StringLoggingPartSetters.cs b/src/Admin/ThingsGateway.Furion/Logging/Internal/StringLoggingPartSetters.cs index c0558d01d..a1b771c66 100644 --- a/src/Admin/ThingsGateway.Furion/Logging/Internal/StringLoggingPartSetters.cs +++ b/src/Admin/ThingsGateway.Furion/Logging/Internal/StringLoggingPartSetters.cs @@ -94,7 +94,7 @@ public sealed partial class StringLoggingPart /// /// 配置日志上下文 /// - /// 建议使用 ConcurrentDictionary 类型 + /// 建议使用 NonBlockingDictionary 类型 /// public StringLoggingPart ScopeContext(IDictionary properties) { diff --git a/src/Admin/ThingsGateway.Furion/Logging/Log.cs b/src/Admin/ThingsGateway.Furion/Logging/Log.cs index e84dc54d4..d1b49d95a 100644 --- a/src/Admin/ThingsGateway.Furion/Logging/Log.cs +++ b/src/Admin/ThingsGateway.Furion/Logging/Log.cs @@ -57,7 +57,7 @@ public static class Log /// /// 配置日志上下文 /// - /// 建议使用 ConcurrentDictionary 类型 + /// 建议使用 NonBlockingDictionary 类型 /// public static (ILogger logger, IDisposable scope) ScopeContext(IDictionary properties) { diff --git a/src/Admin/ThingsGateway.Furion/Schedule/Cancellations/JobCancellationToken.cs b/src/Admin/ThingsGateway.Furion/Schedule/Cancellations/JobCancellationToken.cs index 94bb9baa6..45d47d598 100644 --- a/src/Admin/ThingsGateway.Furion/Schedule/Cancellations/JobCancellationToken.cs +++ b/src/Admin/ThingsGateway.Furion/Schedule/Cancellations/JobCancellationToken.cs @@ -21,7 +21,7 @@ internal sealed class JobCancellationToken : IJobCancellationToken /// /// 取消作业执行 Token 集合 /// - private readonly ConcurrentDictionary _cancellationTokenSources; + private readonly NonBlockingDictionary _cancellationTokenSources; /// /// 作业调度器日志服务 diff --git a/src/Admin/ThingsGateway.Furion/Schedule/Details/JobDetail.Methods.cs b/src/Admin/ThingsGateway.Furion/Schedule/Details/JobDetail.Methods.cs index 551b60b16..580e5b474 100644 --- a/src/Admin/ThingsGateway.Furion/Schedule/Details/JobDetail.Methods.cs +++ b/src/Admin/ThingsGateway.Furion/Schedule/Details/JobDetail.Methods.cs @@ -167,7 +167,7 @@ public partial class JobDetail /// /// 带命名规则的数据库列名 /// - private readonly ConcurrentDictionary _namingColumnNames = new(); + private readonly NonBlockingDictionary _namingColumnNames = new(); /// /// 获取数据库列名 diff --git a/src/Admin/ThingsGateway.Furion/Schedule/Factories/SchedulerFactory.Internal.cs b/src/Admin/ThingsGateway.Furion/Schedule/Factories/SchedulerFactory.Internal.cs index 6cedcc93d..845f90153 100644 --- a/src/Admin/ThingsGateway.Furion/Schedule/Factories/SchedulerFactory.Internal.cs +++ b/src/Admin/ThingsGateway.Furion/Schedule/Factories/SchedulerFactory.Internal.cs @@ -65,7 +65,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory /// /// 作业计划集合 /// - private readonly ConcurrentDictionary _schedulers = new(); + private readonly NonBlockingDictionary _schedulers = new(); /// /// 作业计划构建器集合 diff --git a/src/Admin/ThingsGateway.Furion/Schedule/Triggers/Trigger.Methods.cs b/src/Admin/ThingsGateway.Furion/Schedule/Triggers/Trigger.Methods.cs index 5f752e432..93812c10b 100644 --- a/src/Admin/ThingsGateway.Furion/Schedule/Triggers/Trigger.Methods.cs +++ b/src/Admin/ThingsGateway.Furion/Schedule/Triggers/Trigger.Methods.cs @@ -380,7 +380,7 @@ public partial class Trigger /// /// 带命名规则的数据库列名 /// - private readonly ConcurrentDictionary _namingColumnNames = new(); + private readonly NonBlockingDictionary _namingColumnNames = new(); /// /// 获取数据库列名 diff --git a/src/Admin/ThingsGateway.Furion/SpecificationDocument/Builders/SpecificationDocumentBuilder.cs b/src/Admin/ThingsGateway.Furion/SpecificationDocument/Builders/SpecificationDocumentBuilder.cs index 917b2cb80..e420ce93a 100644 --- a/src/Admin/ThingsGateway.Furion/SpecificationDocument/Builders/SpecificationDocumentBuilder.cs +++ b/src/Admin/ThingsGateway.Furion/SpecificationDocument/Builders/SpecificationDocumentBuilder.cs @@ -83,11 +83,11 @@ public static class SpecificationDocumentBuilder // 初始化常量 _groupOrderRegex = new Regex(@"@(?[0-9]+$)"); - GetActionGroupsCached = new ConcurrentDictionary>(); - GetControllerGroupsCached = new ConcurrentDictionary>(); - GetGroupOpenApiInfoCached = new ConcurrentDictionary(); - GetControllerTagCached = new ConcurrentDictionary(); - GetActionTagCached = new ConcurrentDictionary(); + GetActionGroupsCached = new NonBlockingDictionary>(); + GetControllerGroupsCached = new NonBlockingDictionary>(); + GetGroupOpenApiInfoCached = new NonBlockingDictionary(); + GetControllerTagCached = new NonBlockingDictionary(); + GetActionTagCached = new NonBlockingDictionary(); // 默认分组,支持多个逗号分割 DocumentGroupExtras = new List { ResolveGroupExtraInfo(_specificationDocumentSettings.DefaultGroupName) }; @@ -143,7 +143,7 @@ public static class SpecificationDocumentBuilder /// /// 获取分组信息缓存集合 /// - private static readonly ConcurrentDictionary GetGroupOpenApiInfoCached; + private static readonly NonBlockingDictionary GetGroupOpenApiInfoCached; /// /// 获取分组配置信息 @@ -738,7 +738,7 @@ public static class SpecificationDocumentBuilder /// /// 获取控制器组缓存集合 /// - private static readonly ConcurrentDictionary> GetControllerGroupsCached; + private static readonly NonBlockingDictionary> GetControllerGroupsCached; /// /// 获取控制器分组列表 @@ -773,7 +773,7 @@ public static class SpecificationDocumentBuilder /// /// 缓存集合 /// - private static readonly ConcurrentDictionary> GetActionGroupsCached; + private static readonly NonBlockingDictionary> GetActionGroupsCached; /// /// 获取动作方法分组列表 @@ -808,7 +808,7 @@ public static class SpecificationDocumentBuilder /// /// 缓存集合 /// - private static readonly ConcurrentDictionary GetControllerTagCached; + private static readonly NonBlockingDictionary GetControllerTagCached; /// /// 获取控制器标签 @@ -835,7 +835,7 @@ public static class SpecificationDocumentBuilder /// /// 缓存集合 /// - private static readonly ConcurrentDictionary GetActionTagCached; + private static readonly NonBlockingDictionary GetActionTagCached; /// /// 获取动作方法标签 diff --git a/src/Admin/ThingsGateway.Furion/UnifyResult/UnifyContext.cs b/src/Admin/ThingsGateway.Furion/UnifyResult/UnifyContext.cs index 91455c5cc..b007c9fe5 100644 --- a/src/Admin/ThingsGateway.Furion/UnifyResult/UnifyContext.cs +++ b/src/Admin/ThingsGateway.Furion/UnifyResult/UnifyContext.cs @@ -51,12 +51,12 @@ public static class UnifyContext /// /// 规范化结果提供器 /// - internal static ConcurrentDictionary UnifyProviders = new(); + internal static NonBlockingDictionary UnifyProviders = new(); /// /// 规范化序列化配置 /// - internal static ConcurrentDictionary UnifySerializerSettings = new(); + internal static NonBlockingDictionary UnifySerializerSettings = new(); /// /// 获取异常元数据 diff --git a/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Extensions/ConcurrentDictionaryExtensions.cs b/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Extensions/ConcurrentDictionaryExtensions.cs index 1e4d047b2..613a69049 100644 --- a/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Extensions/ConcurrentDictionaryExtensions.cs +++ b/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Extensions/ConcurrentDictionaryExtensions.cs @@ -14,7 +14,7 @@ using System.Collections.Concurrent; namespace ThingsGateway.Extension; /// -/// 拓展类 +/// 拓展类 /// internal static class ConcurrentDictionaryExtensions { @@ -24,7 +24,7 @@ internal static class ConcurrentDictionaryExtensions /// 字典键类型 /// 字典值类型 /// - /// + /// /// /// /// @@ -36,7 +36,7 @@ internal static class ConcurrentDictionaryExtensions /// /// /// - internal static bool TryUpdate(this ConcurrentDictionary dictionary + internal static bool TryUpdate(this NonBlockingDictionary dictionary , TKey key , Func updateFactory , out TValue? value) diff --git a/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Extensions/IDictionaryExtensions.cs b/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Extensions/IDictionaryExtensions.cs index 8115ec724..22ce4d7cf 100644 --- a/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Extensions/IDictionaryExtensions.cs +++ b/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Extensions/IDictionaryExtensions.cs @@ -241,7 +241,7 @@ internal static class IDictionaryExtensions /// /// 其中键是由值通过给定的选择器函数生成的。 /// - /// + /// /// /// /// @@ -249,7 +249,7 @@ internal static class IDictionaryExtensions /// 键选择器 /// 字典键类型 /// 字典值类型 - internal static void TryAdd(this ConcurrentDictionary dictionary, + internal static void TryAdd(this NonBlockingDictionary dictionary, IEnumerable? values, Func keySelector) where TKey : notnull { diff --git a/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Options/CoreOptions.cs b/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Options/CoreOptions.cs index 933b6ce9c..b99bcd9c1 100644 --- a/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Options/CoreOptions.cs +++ b/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Options/CoreOptions.cs @@ -21,20 +21,20 @@ internal sealed class CoreOptions /// /// 已注册的组件元数据集合 /// - internal readonly ConcurrentDictionary _metadataOfRegistered; + internal readonly NonBlockingDictionary _metadataOfRegistered; /// /// 子选项集合 /// - internal readonly ConcurrentDictionary _optionsInstances; + internal readonly NonBlockingDictionary _optionsInstances; /// /// /// internal CoreOptions() { - _optionsInstances = new ConcurrentDictionary(); - _metadataOfRegistered = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + _optionsInstances = new NonBlockingDictionary(); + _metadataOfRegistered = new NonBlockingDictionary(StringComparer.OrdinalIgnoreCase); EntryComponentTypes = []; } diff --git a/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Reflection/ObjectPropertyGetter.cs b/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Reflection/ObjectPropertyGetter.cs index 2eeb408f1..f279e0722 100644 --- a/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Reflection/ObjectPropertyGetter.cs +++ b/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Reflection/ObjectPropertyGetter.cs @@ -32,7 +32,7 @@ public sealed class ObjectPropertyGetter where T : class /// /// 对象类型实例属性值访问器集合 /// - internal readonly ConcurrentDictionary> _propertyGetters = new(); + internal readonly NonBlockingDictionary> _propertyGetters = new(); /// /// diff --git a/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Reflection/ObjectPropertySetter.cs b/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Reflection/ObjectPropertySetter.cs index b39876e22..5c10db417 100644 --- a/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Reflection/ObjectPropertySetter.cs +++ b/src/Admin/ThingsGateway.Furion/V5_Experience/Core/Reflection/ObjectPropertySetter.cs @@ -32,7 +32,7 @@ public sealed class ObjectPropertySetter where T : class /// /// 对象类型实例属性值设置器集合 /// - internal readonly ConcurrentDictionary> _propertySetters = new(); + internal readonly NonBlockingDictionary> _propertySetters = new(); /// /// diff --git a/src/Admin/ThingsGateway.Furion/V5_Experience/HttpRemote/Declarative/Builders/HttpDeclarativeBuilder.cs b/src/Admin/ThingsGateway.Furion/V5_Experience/HttpRemote/Declarative/Builders/HttpDeclarativeBuilder.cs index 24d1ceb73..018493937 100644 --- a/src/Admin/ThingsGateway.Furion/V5_Experience/HttpRemote/Declarative/Builders/HttpDeclarativeBuilder.cs +++ b/src/Admin/ThingsGateway.Furion/V5_Experience/HttpRemote/Declarative/Builders/HttpDeclarativeBuilder.cs @@ -27,7 +27,7 @@ public sealed class HttpDeclarativeBuilder /// /// HTTP 声明式 提取器集合 /// - internal static readonly ConcurrentDictionary _extractors = new([ + internal static readonly NonBlockingDictionary _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 声明式 提取器集合(冻结) /// /// 该集合用于确保某些 HTTP 声明式提取器始终位于最后。 - internal static readonly ConcurrentDictionary _frozenExtractors = new([ + internal static readonly NonBlockingDictionary _frozenExtractors = new([ new(typeof(MultipartDeclarativeExtractor), new MultipartDeclarativeExtractor()), new(typeof(HttpMultipartFormDataBuilderDeclarativeExtractor), new HttpMultipartFormDataBuilderDeclarativeExtractor()), diff --git a/src/Admin/ThingsGateway.Furion/V5_Experience/HttpRemote/Processors/MessagePackContentProcessor.cs b/src/Admin/ThingsGateway.Furion/V5_Experience/HttpRemote/Processors/MessagePackContentProcessor.cs index 1935c0f0b..5e2a7fcd0 100644 --- a/src/Admin/ThingsGateway.Furion/V5_Experience/HttpRemote/Processors/MessagePackContentProcessor.cs +++ b/src/Admin/ThingsGateway.Furion/V5_Experience/HttpRemote/Processors/MessagePackContentProcessor.cs @@ -27,7 +27,7 @@ public class MessagePackContentProcessor : HttpContentProcessorBase /// /// MessagePack 序列化器委托字典缓存 /// - internal static readonly ConcurrentDictionary> _serializerCache = new(); + internal static readonly NonBlockingDictionary> _serializerCache = new(); /// /// 初始化 MessagePack 序列化器委托 diff --git a/src/Admin/ThingsGateway.NewLife.X/Caching/MemoryCache.cs b/src/Admin/ThingsGateway.NewLife.X/Caching/MemoryCache.cs index 274c96305..4a5ee6646 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Caching/MemoryCache.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Caching/MemoryCache.cs @@ -23,7 +23,7 @@ public class MemoryCache : Cache { #region 属性 /// 缓存核心 - protected ConcurrentDictionary _cache = new(); + protected NonBlockingDictionary _cache = new(); /// 容量。容量超标时,采用LRU机制删除,默认100_000 public Int32 Capacity { get; set; } = 100_000; @@ -379,7 +379,7 @@ public class MemoryCache : Cache /// public override IDictionary GetDictionary(String key) { - var item = GetOrAddItem(key, k => new ConcurrentDictionary()); + var item = GetOrAddItem(key, k => new NonBlockingDictionary()); return item.Visit>() ?? throw new InvalidCastException($"Unable to convert the value of [{key}] from {item.TypeCode} to {typeof(IDictionary)}"); } diff --git a/src/Admin/ThingsGateway.NewLife.X/Caching/Readme.MD b/src/Admin/ThingsGateway.NewLife.X/Caching/Readme.MD index 158cb9fc7..d7fa29c7d 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Caching/Readme.MD +++ b/src/Admin/ThingsGateway.NewLife.X/Caching/Readme.MD @@ -2,7 +2,7 @@ 后续例程与使用说明均以Redis为例,各缓存实现类似。 ### 内存缓存 MemoryCache -MemoryCache核心是并发字典ConcurrentDictionary,由于省去了序列化和网络通信,使得它具有千万级超高性能。 +MemoryCache核心是并发字典NonBlockingDictionary,由于省去了序列化和网络通信,使得它具有千万级超高性能。 MemoryCache支持过期时间,默认容量10万个,未过期key超过该值后,每60秒根据LRU清理溢出部分。 常用于进程内千万级以下数据缓存场景。 diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/CollectionHelper.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/CollectionHelper.cs index 25d204952..331bf36f2 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Collections/CollectionHelper.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/CollectionHelper.cs @@ -44,7 +44,7 @@ public static class CollectionHelper { //if (collection == null) return null; - if (collection is ConcurrentDictionary cdiv && cdiv.Keys is IList list) return list; + if (collection is NonBlockingDictionary cdiv && cdiv.Keys is IList 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 cdiv) return cdiv.Values as IList; - if (collection is ConcurrentDictionary cdiv && cdiv.Values is IList list) return list; + //if (collection is NonBlockingDictionary cdiv) return cdiv.Values as IList; + if (collection is NonBlockingDictionary cdiv && cdiv.Values is IList list) return list; if (collection.Count == 0) return []; lock (collection) diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/ConcurrentHashSet.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/ConcurrentHashSet.cs index bbc5f304e..98716d1cf 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Collections/ConcurrentHashSet.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/ConcurrentHashSet.cs @@ -9,7 +9,7 @@ namespace ThingsGateway.NewLife.Collections; /// public class ConcurrentHashSet : IEnumerable where T : notnull { - private readonly ConcurrentDictionary _dic = new(); + private readonly NonBlockingDictionary _dic = new(); /// 是否空集合 public Boolean IsEmpty => _dic.IsEmpty; diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl.SnapshotImpl.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl.SnapshotImpl.cs new file mode 100644 index 000000000..da2250461 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl.SnapshotImpl.cs @@ -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 + : DictionaryImpl + { + internal override Snapshot GetSnapshot() + { + return new SnapshotImpl(this); + } + + private class SnapshotImpl : Snapshot + { + private readonly DictionaryImpl _table; + + public SnapshotImpl(DictionaryImpl 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)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; + } + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl.cs new file mode 100644 index 000000000..4563a76ec --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl.cs @@ -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 value) + { + if (default(TValue) != null) + { + return new Boxed(value); + } + + return (object)value ?? NULLVALUE; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplBoxed.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplBoxed.cs new file mode 100644 index 000000000..90a8de900 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplBoxed.cs @@ -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 + : DictionaryImpl, TValue> + { + internal DictionaryImplBoxed(int capacity, NonBlockingDictionary topDict) + : base(capacity, topDict) + { + } + + internal DictionaryImplBoxed(int capacity, DictionaryImplBoxed other) + : base(capacity, other) + { + } + + protected override bool TryClaimSlotForPut(ref Boxed entryKey, TKey key) + { + var entryKeyValue = entryKey; + if (entryKeyValue == null) + { + entryKeyValue = Interlocked.CompareExchange(ref entryKey, new Boxed(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 entryKey, Boxed 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 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, TValue> CreateNew(int capacity) + { + return new DictionaryImplBoxed(capacity, this); + } + + protected override TKey keyFromEntry(Boxed entryKey) + { + return entryKey.Value; + } + } + +#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() + internal class Boxed + { + // 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.Default.Equals(this.Value, Unsafe.As>(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.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() +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplInt.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplInt.cs new file mode 100644 index 000000000..855a55d05 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplInt.cs @@ -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 + : DictionaryImpl + { + internal DictionaryImplInt(int capacity, NonBlockingDictionary topDict) + : base(capacity, topDict) + { + } + + internal DictionaryImplInt(int capacity, DictionaryImplInt 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 CreateNew(int capacity) + { + return new DictionaryImplInt(capacity, this); + } + + protected override int keyFromEntry(int entryKey) + { + return entryKey; + } + } + + internal sealed class DictionaryImplIntNoComparer + : DictionaryImpl + { + internal DictionaryImplIntNoComparer(int capacity, NonBlockingDictionary topDict) + : base(capacity, topDict) + { + } + + internal DictionaryImplIntNoComparer(int capacity, DictionaryImplIntNoComparer 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 CreateNew(int capacity) + { + return new DictionaryImplIntNoComparer(capacity, this); + } + + protected override int keyFromEntry(int entryKey) + { + return entryKey; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplLong.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplLong.cs new file mode 100644 index 000000000..819213cd2 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplLong.cs @@ -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 + : DictionaryImpl + { + internal DictionaryImplLong(int capacity, NonBlockingDictionary topDict) + : base(capacity, topDict) + { + } + + internal DictionaryImplLong(int capacity, DictionaryImplLong 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 CreateNew(int capacity) + { + return new DictionaryImplLong(capacity, this); + } + + protected override long keyFromEntry(long entryKey) + { + return entryKey; + } + } + + internal sealed class DictionaryImplLongNoComparer + : DictionaryImpl + { + internal DictionaryImplLongNoComparer(int capacity, NonBlockingDictionary topDict) + : base(capacity, topDict) + { + } + + internal DictionaryImplLongNoComparer(int capacity, DictionaryImplLongNoComparer 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 CreateNew(int capacity) + { + return new DictionaryImplLongNoComparer(capacity, this); + } + + protected override long keyFromEntry(long entryKey) + { + return entryKey; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplNint.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplNint.cs new file mode 100644 index 000000000..04aafb2f8 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplNint.cs @@ -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 + : DictionaryImpl + { + internal DictionaryImplNint(int capacity, NonBlockingDictionary topDict) + : base(capacity, topDict) + { + } + + internal DictionaryImplNint(int capacity, DictionaryImplNint 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 CreateNew(int capacity) + { + return new DictionaryImplNint(capacity, this); + } + + protected override nint keyFromEntry(nint entryKey) + { + return entryKey; + } + } + + internal sealed class DictionaryImplNintNoComparer + : DictionaryImpl + { + internal DictionaryImplNintNoComparer(int capacity, NonBlockingDictionary topDict) + : base(capacity, topDict) + { + } + + internal DictionaryImplNintNoComparer(int capacity, DictionaryImplNintNoComparer 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 CreateNew(int capacity) + { + return new DictionaryImplNintNoComparer(capacity, this); + } + + protected override nint keyFromEntry(nint entryKey) + { + return entryKey; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplRef.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplRef.cs new file mode 100644 index 000000000..e1ec1ef5f --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImplRef.cs @@ -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 + : DictionaryImpl + { + internal DictionaryImplRef(int capacity, NonBlockingDictionary topDict) + : base(capacity, topDict) + { + Debug.Assert(!typeof(TKey).IsValueType); + } + + internal DictionaryImplRef(int capacity, DictionaryImplRef 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(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(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 CreateNew(int capacity) + { + return new DictionaryImplRef(capacity, this); + } + + protected override TKey keyFromEntry(TKey entryKey) + { + return entryKey; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl`2.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl`2.cs new file mode 100644 index 000000000..7e007e656 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl`2.cs @@ -0,0 +1,81 @@ +// Copyright (c) Vladimir Sadov. All rights reserved. +// +// This file is distributed under the MIT License. See LICENSE.md for details. + +#nullable disable + +using System.Collections; +using System.Runtime.CompilerServices; + +namespace System.Collections.Concurrent +{ + internal abstract class DictionaryImpl + : DictionaryImpl + { + private readonly bool _valueIsValueType = typeof(TValue).IsValueType; + + internal IEqualityComparer _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 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 Current + { + get + { + return new KeyValuePair(this._curKey, _curValue); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected TValue FromObjectValue(object obj) + { + // regular value type + if (default(TValue) != null) + { + return Unsafe.As>(obj).Value; + } + + // null + if (obj == NULLVALUE) + { + return default(TValue); + } + + // ref type + if (!_valueIsValueType) + { + return Unsafe.As(ref obj); + } + + // nullable + return (TValue)obj; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl`3.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl`3.cs new file mode 100644 index 000000000..42990c439 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl`3.cs @@ -0,0 +1,1453 @@ +// Copyright (c) Vladimir Sadov. All rights reserved. +// +// This file is distributed under the MIT License. See LICENSE.md for details. + +// +// Core algorithms are based on NonBlockingHashMap, +// written and released to the public domain by Dr.Cliff Click. +// A good overview is here https://www.youtube.com/watch?v=HJ-719EGIts +// + +#nullable disable + +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Collections.Concurrent +{ + internal abstract partial class DictionaryImpl + : DictionaryImpl + { + private readonly Entry[] _entries; + internal DictionaryImpl _newTable; + + protected readonly NonBlockingDictionary _topDict; + protected readonly Counter32 allocatedSlotCount = new Counter32(); + private Counter32 _size; + + internal static readonly bool valueIsAtomic = IsValueAtomicPrimitive(); + + // Sometimes many threads race to create a new very large table. Only 1 + // wins the race, but the losers all allocate a junk large table with + // hefty allocation costs. Attempt to control the overkill here by + // throttling attempts to create a new table. I cannot really block here + // (lest I lose the non-blocking property) but late-arriving threads can + // give the initial resizing thread a little time to allocate the initial + // new table. + // + // count of threads attempting an initial resize + private int _resizers; + + // The next part of the table to copy. It monotonically transits from zero + // to table.length. Visitors to the table can claim 'work chunks' by + // CAS'ing this field up, then copying the indicated indices from the old + // table to the new table. Workers are not required to finish any chunk; + // the counter simply wraps and work is copied duplicately until somebody + // somewhere completes the count. + private int _claimedChunk; + + // Work-done reporting. Used to efficiently signal when we can move to + // the new table. From 0 to length of old table refers to copying from the old + // table to the new. + private int _copyDone; + + [DebuggerDisplay("key = {key}; hash = {hash}; value = {value};")] + [StructLayout(LayoutKind.Sequential)] + public struct Entry + { + internal int hash; + internal TKeyStore key; + internal object value; + } + + private const int MIN_SIZE = 8; + + // targeted time span between resizes. + // if resizing more often than this, try expanding. + private const uint RESIZE_MILLIS_TARGET = 1000; + + // create an empty dictionary + protected abstract DictionaryImpl CreateNew(int capacity); + + // convert key from its storage form (noop or unboxing) used in Key enumarators + protected abstract TKey keyFromEntry(TKeyStore entryKey); + + // compares key with another in its storage form + protected abstract bool keyEqual(TKey key, TKeyStore entryKey); + + // claiming (by writing atomically to the entryKey location) + // or getting existing slot suitable for storing a given key. + protected abstract bool TryClaimSlotForPut(ref TKeyStore entryKey, TKey key); + + // claiming (by writing atomically to the entryKey location) + // or getting existing slot suitable for storing a given key in its store form (could be boxed). + protected abstract bool TryClaimSlotForCopy(ref TKeyStore entryKey, TKeyStore key); + + internal DictionaryImpl(int capacity, NonBlockingDictionary topDict) + { + capacity = Math.Max(capacity, MIN_SIZE); + + capacity = Util.AlignToPowerOfTwo(capacity); + this._entries = new Entry[capacity]; + this._size = new Counter32(); + this._topDict = topDict; + +#if NETSTANDARD2_1_OR_GREATER + if (RuntimeHelpers.IsReferenceOrContainsReferences()) +#else + if (!typeof(TKeyStore).IsValueType) +#endif + { + // do not create a real sweeper just yet. Often it is not needed. + topDict._sweeperInstance = NULLVALUE; + } + + _ = valueIsAtomic; + } + + protected DictionaryImpl(int capacity, DictionaryImpl other) + { + capacity = Util.AlignToPowerOfTwo(capacity); + this._entries = new Entry[capacity]; + this._size = other._size; + this._topDict = other._topDict; + this._keyComparer = other._keyComparer; + } + + /// + /// Determines whether type TValue can be written atomically + /// + private static bool IsValueAtomicPrimitive() + { + // only intereste in primitive value types here. + if (default(TValue) == null) + { + return false; + } + + // + // Section 12.6.6 of ECMA CLI explains which types can be read and written atomically without + // the risk of tearing. + // + // See http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf + // + if (typeof(TValue) == typeof(bool) || + typeof(TValue) == typeof(byte) || + typeof(TValue) == typeof(char) || + typeof(TValue) == typeof(short) || + typeof(TValue) == typeof(int) || + typeof(TValue) == typeof(sbyte) || + typeof(TValue) == typeof(float) || + typeof(TValue) == typeof(ushort) || + typeof(TValue) == typeof(uint) || + typeof(TValue) == typeof(IntPtr) || + typeof(TValue) == typeof(UIntPtr)) + { + return true; + } + + if (typeof(TValue) == typeof(long) || + typeof(TValue) == typeof(double) || + typeof(TValue) == typeof(ulong)) + { + return IntPtr.Size == 8; + } + + return false; + } + + private static uint CurrentTickMillis() + { + return (uint)Environment.TickCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual int hash(TKey key) + { + Debug.Assert(key is not null); + + int h = _keyComparer.GetHashCode(key); + + // ensure that hash never matches TOMBPRIMEHASH, ZEROHASH, SPECIAL_HASH_BITS or 0 + return h | (SPECIAL_HASH_BITS | (1 << 29)); + } + + internal sealed override int Count + { + get + { + return this.Size; + } + } + + internal sealed override void Clear() + { + var newTable = CreateNew(MIN_SIZE); + newTable._size = new Counter32(); + _topDict._table = newTable; + } + + /// + /// returns null if value is not present in the table + /// otherwise returns the actual value or NULLVALUE if null is the actual value + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override object TryGetValue(TKey key) + { + int fullHash = this.hash(key); + var curTable = this; + + tryWithNewTable: + + var lenMask = curTable._entries.Length - 1; + int idx = ReduceHashToIndex(fullHash, lenMask); + + // Main spin/reprobe loop + int reprobeCount = 0; + while (true) + { + ref var entry = ref curTable._entries[idx]; + + // an entry is never reused for a different key + // key/value/hash all read atomically and order of reads is unimportant + + // is this our slot? + if (fullHash == entry.hash && keyEqual(key, entry.key)) + { + // read the value before reading the _newTable. + // if the new table is null after we already have value, + // then the value cannot be forwarded + // this also orders reads from the table. + var entryValue = Volatile.Read(ref entry.value); + if (EntryValueNullOrDead(entryValue)) + { + break; + } + + // "READ BARRIER", if copying has started, we must help with copying and + // read from the new table. + + // if no new table, no need to check for primed value, + // but TOMBPRIME is possible when sweeping, check for that + if ((curTable._newTable == null && entryValue != TOMBPRIME) || + entryValue.GetType() != typeof(Prime)) + { + return entryValue; + } + + // found a prime, that means the copying or sweeping has started, + // help and retry in the new table + curTable = curTable.CopySlotAndGetNewTable(ref entry, shouldHelp: true); + goto tryWithNewTable; + } + + if (entry.hash == 0) + { + // the slot has not been claimed - the rest of the bucket is empty + break; + } + + // get, put and remove must have the same key lookup logic. + // But only 'put' needs to force a table-resize for a too-long key-reprobe sequence + // hitting reprobe limit or finding TOMBPRIMEHASH here means that the key is not in this table, + // but there could be more in the new table + if (entry.hash == TOMBPRIMEHASH || reprobeCount >= ReprobeLimit(lenMask)) + { + if (curTable._newTable != null) + { + curTable.HelpCopy(); + curTable = curTable._newTable; + goto tryWithNewTable; + } + + // no new table, so this is a miss + break; + } + + // quadratic reprobe + reprobeCount++; + curTable.ResizeOnReprobeCheck(reprobeCount); + idx = (idx + reprobeCount) & lenMask; + } + + return null; + } + + /// + /// returns true if value was removed from the table. + /// oldVal contains original value or default(TValue), if it was not present in the table + /// + internal sealed override bool RemoveIfMatch(TKey key, ref TValue oldVal, ValueMatch match) + { + Debug.Assert( + match == ValueMatch.NotNullOrDead || + match == ValueMatch.OldValue || + match == ValueMatch.Any); // same as NotNullOrDead, but not reporting the old value + + int fullHash = this.hash(key); + var curTable = this; + + tryWithNewTable: + + var lenMask = curTable._entries.Length - 1; + int idx = ReduceHashToIndex(fullHash, lenMask); + ref Entry entry = ref curTable._entries[idx]; + + // Main spin/reprobe loop + int reprobeCount = 0; + while (true) + { + // an entry is never reused for a different key + // key/value/hash all read atomically and order of reads is unimportant + var entryHash = entry.hash; + + // is this our slot? + if (fullHash == entryHash && curTable.keyEqual(key, entry.key)) + { + break; + } + + if (entryHash == 0) + { + // Found an unassigned slot - which means this + // key has never been in this table. + oldVal = default; + goto FAILED; + } + + // get, put and remove must have the same key lookup logic. + // But only 'put' needs to force a table-resize for a too-long key-reprobe sequence + // hitting reprobe limit or finding TOMBPRIMEHASH here means that the key is not in this table, + // but there could be more in the new table + if (entryHash == TOMBPRIMEHASH || reprobeCount >= ReprobeLimit(lenMask)) + { + if (curTable._newTable != null) + { + curTable.HelpCopy(); + curTable = curTable._newTable; + goto tryWithNewTable; + } + + // no new table, so this is a miss + goto FAILED; + } + + // quadratic reprobing + reprobeCount++; + curTable.ResizeOnReprobeCheck(reprobeCount); + idx = (idx + reprobeCount) & lenMask; + entry = ref curTable._entries[idx]; + } + + // Found the proper Key slot, now update the Value. + // We never put a null, so Value slots monotonically move from null to + // not-null (deleted Values use Tombstone). + + // volatile read to make sure we read the element before we read the _newTable + // that would guarantee that as long as _newTable == null, entryValue cannot be forwarded. + var entryValue = Volatile.Read(ref entry.value); + var newTable = curTable._newTable; + + // See if we are moving to a new table. + // If so, copy our slot and retry in the new table. + // Seeing TOMBPRIME entry while no newTable means the slot is in a process of being deleted + // Let CopySlotAndGetNewTable handle that case too. + if (newTable != null || entryValue == TOMBPRIME) + { + var newTable1 = curTable.CopySlotAndGetNewTable(ref entry, shouldHelp: true); + Debug.Assert(newTable == newTable1 || (newTable == null && newTable1 == this)); + curTable = newTable1; + goto tryWithNewTable; + } + + // We are finally prepared to update the existing table + while (true) + { + Debug.Assert(entryValue is not Prime); + + // can't remove if nothing is there + if (EntryValueNullOrDead(entryValue)) + { + oldVal = default; + goto FAILED; + } + + if (ValueIsAtomicPrimitive() && match != ValueMatch.Any) + { + // must freeze before removing or before checking for value match + // unless it is "Any" case where we have no witnesses of the old value + // and can assume all writes in the current box "happened before" the remove. + Unsafe.As>(entryValue).Freeze(); + } + + if (match == ValueMatch.OldValue) + { + TValue unboxedEntryValue = FromObjectValue(entryValue); + if (!EqualityComparer.Default.Equals(oldVal, unboxedEntryValue)) + { + oldVal = unboxedEntryValue; + goto FAILED; + } + } + + // Actually change the Value + var prev = Interlocked.CompareExchange(ref entry.value, TOMBSTONE, entryValue); + if (prev == entryValue) + { + // CAS succeeded - we removed! + if (match == ValueMatch.NotNullOrDead) + { + oldVal = FromObjectValue(prev); + } + + // Adjust the size + curTable._size.Decrement(); + SweepCheck(); + + return true; + } + + // If a Prime'd value got installed, we need to re-run on the new table. + Debug.Assert(prev != null); + if (prev.GetType() == typeof(Prime)) + { + curTable = curTable.CopySlotAndGetNewTable(ref entry, shouldHelp: true); + goto tryWithNewTable; + } + + // Otherwise we lost the CAS to another racing put/remove. + // Simply retry from the start. + entryValue = prev; + } + + FAILED: + return false; + } + + // 1) finds or creates a slot for the key + // 2) sets the slot value to the newVal if original value meets oldVal and match condition + // 3) returns true if the value was actually changed + // Note that pre-existence of the slot is irrelevant + // since slot without a value is as good as no slot at all + internal sealed override bool PutIfMatch(TKey key, TValue newVal, ref TValue oldVal, ValueMatch match) + { + Debug.Assert( + match == ValueMatch.NullOrDead || + match == ValueMatch.OldValue || + match == ValueMatch.Any); + + int fullHash = this.hash(key); + var curTable = this; + + tryWithNewTable: + + var lenMask = curTable._entries.Length - 1; + int idx = ReduceHashToIndex(fullHash, lenMask); + ref Entry entry = ref curTable._entries[idx]; + + // Spin till we get a slot for the key or force a resizing. + int reprobeCount = 0; + while (true) + { + // an entry is never reused for a different key + // key/value/hash all read atomically and order of reads is unimportant + var entryHash = entry.hash; + if (entryHash == 0) + { + // Found an unassigned slot - which means this key has never been in this table. + // claim the hash first + Debug.Assert(fullHash != 0); + entryHash = Interlocked.CompareExchange(ref entry.hash, fullHash, 0); + if (entryHash == 0) + { + entryHash = fullHash; + if (entryHash == ZEROHASH) + { + // "added" entry for zero key + curTable.allocatedSlotCount.Increment(); + break; + } + } + } + + if (entryHash == fullHash) + { + // hash is good, one way or another, + // try claiming the slot for the key + if (curTable.TryClaimSlotForPut(ref entry.key, key)) + { + break; + } + } + + // here we know that this slot does not map to our key and must reprobe or resize + // hitting reprobe limit or finding TOMBPRIMEHASH here means that the key is not in this table, + // but there could be more in the new table + if (entryHash == TOMBPRIMEHASH || reprobeCount >= ReprobeLimit(lenMask)) + { + // start resize or get new table if resize is already in progress + var newTable1 = curTable.Resize(isForReprobe: true); + // help along an existing copy + curTable.HelpCopy(); + curTable = newTable1; + goto tryWithNewTable; + } + + // quadratic reprobing + reprobeCount++; + curTable.ResizeOnReprobeCheck(reprobeCount); + idx = (idx + reprobeCount) & lenMask; + entry = ref curTable._entries[idx]; + } + + // Found the proper Key slot, now update the Value. + // We never put a null, so Value slots monotonically move from null to + // not-null (deleted Values use Tombstone). + + // volatile read to make sure we read the element before we read the _newTable + // that would guarantee that as long as _newTable == null, entryValue cannot be forwarded. + var entryValue = Volatile.Read(ref entry.value); + + // See if we want to move to a new table (to avoid high average re-probe counts). + // We only check on the initial set of a Value from null to + // not-null (i.e., once per key-insert). + var newTable = curTable._newTable; + + // newTable == entryValue only when both are nulls + if ((object)newTable == (object)entryValue && + curTable.TableIsCrowded()) + { + // Force the new table copy to start + newTable = curTable.Resize(isForReprobe: false); + Debug.Assert(curTable._newTable != null && newTable == curTable._newTable); + } + + // See if we are moving to a new table. + // If so, copy our slot and retry in the new table. + // Seeing TOMBPRIME entry while no newTable means the slot is in a process of being deleted + // Let CopySlotAndGetNewTable handle that case too. + if (newTable != null || entryValue == TOMBPRIME) + { + var newTable1 = curTable.CopySlotAndGetNewTable(ref entry, shouldHelp: true); + Debug.Assert(newTable == newTable1 || (newTable == null && newTable1 == this)); + curTable = newTable1; + goto tryWithNewTable; + } + + // We are finally prepared to update the existing table + while (true) + { + Debug.Assert(!(entryValue is Prime)); + var entryValueNullOrDead = EntryValueNullOrDead(entryValue); + + switch (match) + { + case ValueMatch.Any: + if (ValueIsAtomicPrimitive() && + !entryValueNullOrDead && + Unsafe.As>(entryValue).TryVolatileWrite(newVal)) + { + return true; + } + break; + + case ValueMatch.OldValue: + if (entryValueNullOrDead) + { + goto FAILED; + } + + if (ValueIsAtomicPrimitive() && + Unsafe.As>(entryValue).TryCompareExchange(oldVal, newVal, out var changed)) + { + return changed; + } + + if (ValueIsAtomicPrimitive()) + { + // we could not change the value in place (this is rare), fallback to replacing the whole box. + Unsafe.As>(entryValue).Freeze(); + } + + TValue unboxedEntryValue = FromObjectValue(entryValue); + if (!EqualityComparer.Default.Equals(oldVal, unboxedEntryValue)) + { + goto FAILED; + } + break; + + default: + Debug.Assert(match == ValueMatch.NullOrDead); + if (!entryValueNullOrDead) + { + // this is the only case where caller expects to see oldVal + // NB: No need to freeze here. This is keyed on mere presence of the value. + oldVal = FromObjectValue(entryValue); + goto FAILED; + } + break; + } + + // Actually change the Value + object newValObj = ToObjectValue(newVal); + var prev = Interlocked.CompareExchange(ref entry.value, newValObj, entryValue); + if (prev == entryValue) + { + // CAS succeeded - we did the update! + // Adjust sizes + if (entryValueNullOrDead) + { + curTable._size.Increment(); + } + + return true; + } + // Else CAS failed + + // If a Prime'd value got installed, we need to re-run the put on the new table. + Debug.Assert(prev != null); + if (prev.GetType() == typeof(Prime)) + { + curTable = curTable.CopySlotAndGetNewTable(ref entry, shouldHelp: true); + goto tryWithNewTable; + } + + // Otherwise we lost the CAS to another racing put. + // Simply retry from the start. + entryValue = prev; + } + + FAILED: + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool ValueIsAtomicPrimitive() + { + return default(TValue) != null && valueIsAtomic; + } + + internal sealed override TValue GetOrAdd(TKey key, Func valueFactory) + { + object newValObj = null; + TValue result = default(TValue); + + int fullHash = this.hash(key); + var curTable = this; + + TRY_WITH_NEW_TABLE: + + var lenMask = curTable._entries.Length - 1; + int idx = ReduceHashToIndex(fullHash, lenMask); + ref Entry entry = ref curTable._entries[idx]; + + // Spin till we get a slot for the key or force a resizing. + int reprobeCount = 0; + while (true) + { + // hash and key are CAS-ed down and follow a specific sequence of states. + // hence the order of their reads is irrelevant and they do not need to be volatile + var entryHash = entry.hash; + if (entryHash == 0) + { + // Found an unassigned slot - which means this + // key has never been in this table. + // Slot is completely clean, claim the hash first + Debug.Assert(fullHash != 0); + entryHash = Interlocked.CompareExchange(ref entry.hash, fullHash, 0); + if (entryHash == 0) + { + entryHash = fullHash; + if (entryHash == ZEROHASH) + { + // "added" entry for zero key + curTable.allocatedSlotCount.Increment(); + break; + } + } + } + + if (entryHash == fullHash) + { + // hash is good, one way or another, + // try claiming the slot for the key + if (curTable.TryClaimSlotForPut(ref entry.key, key)) + { + break; + } + } + + // here we know that this slot does not map to our key + // and must reprobe or resize + // hitting reprobe limit or finding TOMBPRIMEHASH here means that the key is not in this table, + // but there could be more in the new table + if (entryHash == TOMBPRIMEHASH || reprobeCount >= ReprobeLimit(lenMask)) + { + // start resize or get new table if resize is already in progress + var newTable1 = curTable.Resize(isForReprobe: true); + // help along an existing copy + curTable.HelpCopy(); + curTable = newTable1; + goto TRY_WITH_NEW_TABLE; + } + + // quadratic reprobing + reprobeCount++; + curTable.ResizeOnReprobeCheck(reprobeCount); + idx = (idx + reprobeCount) & lenMask; + entry = ref curTable._entries[idx]; + } + + // Found the proper Key slot, now update the Value. + // We never put a null, so Value slots monotonically move from null to + // not-null (deleted Values use Tombstone). + + // volatile read to make sure we read the element before we read the _newTable + // that would guarantee that as long as _newTable == null, entryValue cannot be forwarded. + var entryValue = Volatile.Read(ref entry.value); + + // See if we want to move to a new table (to avoid high average re-probe counts). + // We only check on the initial set of a Value from null to + // not-null (i.e., once per key-insert). + var newTable = curTable._newTable; + + // newTable == entryValue only when both are nulls + if ((object)newTable == (object)entryValue && + curTable.TableIsCrowded()) + { + // Force the new table copy to start + newTable = curTable.Resize(isForReprobe: false); + Debug.Assert(curTable._newTable != null && curTable._newTable == newTable); + } + + // See if we are moving to a new table. + // If so, copy our slot and retry in the new table. + // Seeing TOMBPRIME entry while no newTable means the slot is in a process of being deleted + // Let CopySlotAndGetNewTable handle that case too. + if (newTable != null || entryValue == TOMBPRIME) + { + var newTable1 = curTable.CopySlotAndGetNewTable(ref entry, shouldHelp: true); + Debug.Assert(newTable == newTable1); + curTable = newTable; + goto TRY_WITH_NEW_TABLE; + } + + if (!EntryValueNullOrDead(entryValue)) + { + goto GOT_PREV_VALUE; + } + + // prev value is null or dead. + // let's try install new value + newValObj ??= ToObjectValue(result = valueFactory(key)); + while (true) + { + Debug.Assert(entryValue is not Prime); + + // Actually change the Value + var prev = Interlocked.CompareExchange(ref entry.value, newValObj, entryValue); + if (prev == entryValue) + { + // CAS succeeded - we did the update! + // Adjust sizes + curTable._size.Increment(); + goto DONE; + } + // Else CAS failed + + // If a Prime'd value got installed, we need to re-run on the new table. + Debug.Assert(prev != null); + if (prev.GetType() == typeof(Prime)) + { + curTable = curTable.CopySlotAndGetNewTable(ref entry, shouldHelp: true); + goto TRY_WITH_NEW_TABLE; + } + + // Otherwise we lost the CAS to another racing put. + entryValue = prev; + if (entryValue != TOMBSTONE) + { + goto GOT_PREV_VALUE; + } + } + + GOT_PREV_VALUE: + result = FromObjectValue(entryValue); + + DONE: + return result; + } + + private bool PutSlotCopy(TKeyStore key, object value, int fullHash) + { + Debug.Assert(key != null); + Debug.Assert(value != TOMBSTONE); + Debug.Assert(value != null); + Debug.Assert(!(value is Prime)); + + var curTable = this; + + TRY_WITH_NEW_TABLE: + + var lenMask = curTable._entries.Length - 1; + int idx = ReduceHashToIndex(fullHash, lenMask); + ref Entry entry = ref curTable._entries[idx]; + + // Spin till we get a slot for the key or force a resizing. + int reprobeCount = 0; + while (true) + { + var entryHash = entry.hash; + if (entryHash == 0) + { + // Slot is completely clean, claim the hash + Debug.Assert(fullHash != 0); + entryHash = Interlocked.CompareExchange(ref entry.hash, fullHash, 0); + if (entryHash == 0) + { + entryHash = fullHash; + if (entryHash == ZEROHASH) + { + // "added" entry for zero key + curTable.allocatedSlotCount.Increment(); + break; + } + } + } + + if (entryHash == fullHash) + { + // hash is good, one way or another, claim the key + if (curTable.TryClaimSlotForCopy(ref entry.key, key)) + { + break; + } + } + + // this slot contains a different key + + // here we know that this slot does not map to our key + // and must reprobe or resize + // hitting reprobe limit or finding TOMBPRIMEHASH here means that + // we will not find an appropriate slot in this table + // but there could be more in the new one + if (entryHash == TOMBPRIMEHASH || reprobeCount >= ReprobeLimit(lenMask)) + { + var resized = curTable.Resize(isForReprobe: true); + curTable = resized; + goto TRY_WITH_NEW_TABLE; + } + + // quadratic reprobing + reprobeCount++; + // no resize check on reprobe needed. + // we always insert a new value (or somebody else inserts) + idx = (idx + reprobeCount) & lenMask; + entry = ref curTable._entries[idx]; + } + + // Found the proper Key slot, now update the Value. + + // volatile read to make sure we read the element before we read the _newTable + // that would guarantee that as long as _newTable == null, entryValue cannot be forwarded. + var entryValue = Volatile.Read(ref entry.value); + + // See if we want to move to a new table (to avoid high average re-probe counts). + // We only check on the initial set of a Value from null to + // not-null (i.e., once per key-insert). + var newTable = curTable._newTable; + + // newTable == entryValue only when both are nulls + if ((object)newTable == (object)entryValue && + curTable.TableIsCrowded()) + { + // Force the new table copy to start + newTable = curTable.Resize(isForReprobe: false); + Debug.Assert(curTable._newTable != null && curTable._newTable == newTable); + } + + // See if we are moving to a new table. + // If so, copy our slot and retry in the new table. + // Seeing TOMBPRIME entry while no newTable means the slot is in a process of being deleted + // Let CopySlotAndGetNewTable handle that case too. + if (newTable != null || entryValue == TOMBPRIME) + { + var newTable1 = curTable.CopySlotAndGetNewTable(ref entry, shouldHelp: false); + Debug.Assert(newTable == newTable1); + curTable = newTable; + goto TRY_WITH_NEW_TABLE; + } + + // We are finally prepared to update the existing table + // if entry value is null and our CAS succeeds - we did the update! + // otherwise someone else copied the value. + // table-copy does not (effectively) increase the number of live k/v pairs + // so no need to update size + return entry.value == null && + Interlocked.CompareExchange(ref entry.value, value, null) == null; + } + + // check once in a while if a table might benefit from resizing. + // one reason for this is that crowdedness check uses estimated counts + // so we do not always catch this on key inserts. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ResizeOnReprobeCheck(int reprobeCount) + { + // must be ^2 - 1 + const int reprobeCheckPeriod = 16 - 1; + + // once per reprobeCheckPeriod, check if the table is crowded + // and initiate a resize + if ((reprobeCount & reprobeCheckPeriod) == 0) + { + ReprobeResizeCheckSlow(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void ReprobeResizeCheckSlow() + { + if (this.TableIsCrowded()) + { + this.Resize(isForReprobe: false); + this.HelpCopy(); + } + } + + /////////////////////////////////////////////////////////// + // Resize support + /////////////////////////////////////////////////////////// + + internal int Size + { + get + { + // counter does not lose counts, but reports of increments/decrements can be delayed + // it might be confusing if we ever report negative size. + var size = _size.Value; + var negMask = ~(size >> 31); + return size & negMask; + } + } + + internal int EstimatedSlotsUsed + { + get + { + return (int)allocatedSlotCount.EstimatedValue; + } + } + + internal bool TableIsCrowded() + { + // 75% utilization, switch to a bigger table + return EstimatedSlotsUsed >= (_entries.Length / 4 * 3); + } + + // Help along an existing resize operation. This is just a fast cut-out + // wrapper, to encourage inlining for the fast no-copy-in-progress case. + private void HelpCopyIfNeeded() + { + if (this._newTable != null) + { + this.HelpCopy(copy_all: false); + } + } + + // Help along an existing resize operation. + internal void HelpCopy(bool copy_all = false) + { + var newTable = this._newTable; + var oldEntries = this._entries; + int toCopy = oldEntries.Length; + +#if DEBUG + const int CHUNK_SIZE = 16; +#else + const int CHUNK_SIZE = 1024; +#endif + int MIN_COPY_WORK = Math.Min(toCopy, CHUNK_SIZE); // Limit per-thread work + + bool panic = false; + int claimedChunk = -1; + + while (this._copyDone < toCopy) + { + // Still needing to copy? + // Carve out a chunk of work. + if (!panic) + { + claimedChunk = this._claimedChunk; + + while (true) + { + // panic check + // We "panic" if we have tried TWICE to copy every slot - and it still + // has not happened. i.e., twice some thread somewhere claimed they + // would copy 'slot X' (by bumping _copyIdx) but they never claimed to + // have finished (by bumping _copyDone). Our choices become limited: + // we can wait for the work-claimers to finish (and become a blocking + // algorithm) or do the copy work ourselves. Tiny tables with huge + // thread counts trying to copy the table often 'panic'. + if (claimedChunk > (toCopy / (CHUNK_SIZE / 2))) + { + panic = true; + break; + } + + var alreadyClaimed = Interlocked.CompareExchange(ref this._claimedChunk, claimedChunk + 1, claimedChunk); + if (alreadyClaimed == claimedChunk) + { + break; + } + + claimedChunk = alreadyClaimed; + } + } + else + { + // we went through the whole table in panic mode + // there cannot be possibly anything left to copy. + if (claimedChunk > ((toCopy / (CHUNK_SIZE / 2)) + toCopy / CHUNK_SIZE)) + { + _copyDone = toCopy; + PromoteNewTable(); + return; + } + + claimedChunk++; + } + + // We now know what to copy. Try to copy. + int workdone = 0; + int copyStart = claimedChunk * CHUNK_SIZE; + for (int i = 0; i < MIN_COPY_WORK; i++) + { + if (this._copyDone >= toCopy) + { + PromoteNewTable(); + return; + } + + if (CopySlot(ref oldEntries[(copyStart + i) & (toCopy - 1)], newTable)) + { + workdone++; + } + } + + if (workdone > 0) + { + // See if we can promote + var copyDone = Interlocked.Add(ref this._copyDone, workdone); + + // Check for copy being ALL done, and promote. + if (copyDone >= toCopy) + { + PromoteNewTable(); + } + } + + if (!(copy_all | panic)) + { + return; + } + } + + // Extra promotion check, in case another thread finished all copying + // then got stalled before promoting. + PromoteNewTable(); + } + + private void PromoteNewTable() + { + // Looking at the top-level table? + // Note that we might have + // nested in-progress copies and manage to finish a nested copy before + // finishing the top-level copy. We only promote top-level copies. + if (_topDict._table == this) + { + // Attempt to promote + if (Interlocked.CompareExchange(ref _topDict._table, this._newTable, this) == this) + { + _topDict._lastResizeTickMillis = CurrentTickMillis(); + } + } + } + + // Copy slot 'idx' from the old table to the new table. If this thread + // confirmed the copy, update the counters and check for promotion. + // + // Returns the result of reading the new table, mostly as a + // convenience to callers. We come here with 1-shot copy requests + // typically because the caller has found a Prime, and has not yet read + // the new table - which must have changed from null-to-not-null + // before any Prime appears. So the caller needs to read the new table + // field to retry his operation in the new table, but probably has not + // read it yet. + internal DictionaryImpl CopySlotAndGetNewTable(ref Entry entry, bool shouldHelp) + { + // However this could be just an entry partially swept. + // In such case treat the value as being copied (into oblivion) and return the same table back to retry. + var newTable = this._newTable; + + // We're here because the caller saw a Prime or new table, which implies copying or sweeping is in progress. + Debug.Assert(entry.value is Prime || newTable != null); + + if (newTable == null) + { + Debug.Assert(!typeof(TKeyStore).GetTypeInfo().IsValueType); + // help with sweeping in case the sweeper thread is stuck. We do not want to come here again. + // we write unconditionally though, since this is a relatively rare case. + entry.hash = SPECIAL_HASH_BITS; + entry.key = default(TKeyStore); + + return this; + } + + if (CopySlot(ref entry, newTable)) + { + // Record the slot copied + var copyDone = Interlocked.Increment(ref this._copyDone); + + // Check for copy being ALL done, and promote. + if (copyDone >= this._entries.Length) + { + PromoteNewTable(); + return newTable; + } + } + + // Generically help along any copy (except if called recursively from a helper) + if (shouldHelp) + { + this.HelpCopy(); + } + + return newTable; + } + + // Copy one K/V pair from old table to new table. + // Returns true if we actually did the copy. + // Regardless, once this returns, the copy is available in the new table and + // slot in the old table is no longer usable. + private static bool CopySlot(ref Entry oldEntry, DictionaryImpl newTable) + { + Debug.Assert(newTable != null); + + // Blindly set the hash from 0 to TOMBPRIMEHASH, to eagerly stop + // fresh put's from claiming new slots in the old table when the old + // table is mid-resize. + var hash = oldEntry.hash; + if (hash == 0) + { + hash = Interlocked.CompareExchange(ref oldEntry.hash, TOMBPRIMEHASH, 0); + if (hash == 0) + { + // slot was not claimed, copy is done here + return true; + } + } + + if (hash == TOMBPRIMEHASH) + { + // slot was trivially copied, but not by us + return false; + } + + // Prevent new values from appearing in the old table. + // Put a forwarding entry, to prevent further updates. + // NOTE: Read of the value below must happen before reading of the key, + // however this read does not need to be volatile since we will have + // some fences in between reads. + object oldval = oldEntry.value; + + // already boxed? + Prime box = oldval as Prime; + if (box != null) + { + // volatile read here since we need to make sure + // that the key read below happens after we have read oldval above + // (this read is a dependednt read after oldval, and reading the key happens-after) + Volatile.Read(ref box.originalValue); + } + else + { + do + { + box = EntryValueNullOrDead(oldval) ? + TOMBPRIME : + new Prime(oldval); + + // CAS down a box'd version of oldval + // also works as a complete fence between reading the value and the key + object prev = Interlocked.CompareExchange(ref oldEntry.value, box, oldval); + + if (prev == oldval) + { + // If we made the Value slot hold a TOMBPRIME, then we both + // prevented further updates here but also the (absent) + // oldval is vacuously available in the new table. We + // return with true here: any thread looking for a value for + // this key can correctly go straight to the new table and + // skip looking in the old table. + if (box == TOMBPRIME) + { + return true; + } + + // Break loop; oldval is now boxed by us + // it still needs to be copied into the new table. + break; + } + + oldval = prev; + box = oldval as Prime; + } + while (box == null); + } + + if (box == TOMBPRIME) + { + // Copy already complete here, but not by us. + return false; + } + + // Copy the value into the new table, but only if we overwrite a null. + // If another value is already in the new table, then somebody else + // wrote something there and that write is happens-after any value that + // appears in the old table. If putIfMatch does not find a null in the + // new table - somebody else should have recorded the null-not_null + // transition in this copy. + object originalValue = box.originalValue; + Debug.Assert(originalValue != TOMBSTONE); + + // since we have a real value, there must be a nontrivial key in the table. + // regular read is ok because value is always CASed down after the key + // and we ensured that we read the key after the value with fences above + var key = oldEntry.key; + bool copiedIntoNew = newTable.PutSlotCopy(key, originalValue, hash); + + // Finally, now that any old value is exposed in the new table, we can + // forever hide the old-table value by gently inserting TOMBPRIME value. + // This will stop other threads from uselessly attempting to copy this slot + // (i.e., it's a speed optimization not a correctness issue). + if (oldEntry.value != TOMBPRIME) + { + oldEntry.value = TOMBPRIME; + } + + // if we failed to copy, it means something has already appeared in + // the new table and old value should have been copied before that (not by us). + return copiedIntoNew; + } + + // kick off resizing, if not started already, and return the new table. + private DictionaryImpl Resize(bool isForReprobe) + { + // Check for resize already in progress, probably triggered by another thread. + // reads of this._newTable in Resize are not volatile + // we are just opportunistically checking if a new table has arrived. + return this._newTable ?? ResizeImpl(isForReprobe); + } + + // Resizing after too many probes. "How Big???" heuristics are here. + // Callers will (not this routine) help any in-progress copy. + // Since this routine has a fast cutout for copy-already-started, callers + // MUST 'help_copy' lest we have a path which forever runs through + // 'resize' only to discover a copy-in-progress which never progresses. + private DictionaryImpl ResizeImpl(bool isForReprobe) + { + const int MAX_SIZE = 1 << 30; + const int MAX_CHURN_SIZE = 1 << 20; + + // First up: compute new table size. + int oldlen = this._entries.Length; + + // First size estimate is 4x of the current size + int oldsz = Size; + int newsz = oldsz <= (MAX_SIZE / 4) ? + oldsz * 4 : + oldsz <= (MAX_SIZE / 2) ? + oldsz * 2 : + oldsz; + + newsz = Math.Max(newsz, MIN_SIZE); + + if (isForReprobe) + { + // if half slots are dead, just do regular resize + // otherwise we want to double the length to not come here too soon + if (allocatedSlotCount.Value < oldsz * 2) + { + if (oldlen <= (MAX_SIZE / 2)) + { + newsz = Math.Max(newsz, oldlen * 2); + } + } + } + + if (newsz <= oldlen) + { + // if new table would shrink or hold steady, + // we must be resizing because of churn. + // target churn based resize rate to be about 1 per RESIZE_TICKS_TARGET + var resizeSpan = CurrentTickMillis() - _topDict._lastResizeTickMillis; + + // note that CurrentTicks() will wrap around every 50 days. + // For our purposes that is tolerable since it just + // adds a possibility that in some rare cases a churning resize will not be + // considered a churning one. + if (resizeSpan < RESIZE_MILLIS_TARGET) + { + // last resize too recent, expand + newsz = oldlen < MAX_CHURN_SIZE ? oldlen << 1 : oldlen; + } + else + { + // do not allow shrink too fast + newsz = Math.Max(newsz, (int)((long)oldlen * RESIZE_MILLIS_TARGET / resizeSpan)); + } + } + + // Align up to a power of 2 + newsz = Util.AlignToPowerOfTwo(newsz); + + // Estimate new array size. This is used for limiting spinwaiting when allocating large tables. + // Size calculation: 2 64bit words per table entry for simplicity. + int kBs = newsz >> (10 - 4); + + var newTable = this._newTable; + + // Now, if allocation is big enough, + // limit the number of threads actually allocating memory to a + // handful - lest we have 750 threads all trying to allocate a giant + // resized array. + // conveniently, Increment is also a full fence + if (kBs > 0 && Interlocked.Increment(ref _resizers) >= 2) + { + // Already 2 guys trying; wait and see + // See if resize is already in progress + if (newTable != null) + { + return newTable; // Use the new table already + } + + // spinwait for other allocators at the rate of: 1 msec / 32 kB + // 32 kB - > 1 msec + // MAX_SIZE -> 512 sec. + SpinWait.SpinUntil(() => this._newTable != null, kBs >> 5); + } + + // Last check, since the 'new' below is expensive and there is a chance + // that another thread slipped in a new table while we ran the heuristic. + newTable = this._newTable; + // See if resize is already in progress + if (newTable != null) + { + return newTable; // Use the new table already + } + + newTable = this.CreateNew(newsz); + + // The new table must be CAS'd in to ensure only 1 winner + var prev = this._newTable ?? + Interlocked.CompareExchange(ref this._newTable, newTable, null); + + if (prev != null) + { + return prev; + } + else + { + //System.Console.WriteLine(newsz + " :" + isForReprobe + " size:" + oldsz); + return newTable; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SweepCheck() + { + if (_topDict._sweepRequests == 0) + { + _topDict._sweepRequests = 1; + Sweeper.TryRearm(this); + } + } + + private class Sweeper + { + private DictionaryImpl _dict; + + public static void TryRearm(DictionaryImpl dict) + { + ref var sweeperLocation = ref dict._topDict._sweeperInstance; + var obj = sweeperLocation; + if (obj != null && Interlocked.CompareExchange(ref sweeperLocation, null, obj) == obj) + { + Sweeper sweeper = obj as Sweeper ?? new Sweeper(); + sweeper._dict = dict; + GC.ReRegisterForFinalize(sweeper); + } + } + + ~Sweeper() + { + Task.Run(() => Sweep()); + } + + private void Sweep() + { + var dict = _dict; + this._dict = null; + + if (dict == null) + { + // this could happen when table is destroyed + return; + } + + // any key removed after we start sweeping may not be swept + // signal to future removers that they need another request. + // note that remove is a CAS, so remover will see this value + // after removing. + Interlocked.Exchange(ref dict._topDict._sweepRequests, 0); + + var entries = dict._entries; + for (int i = 0; i < entries.Length; i++) + { + // if resizing, just help to resize instead + if (dict._newTable != null) + { + do + { + dict.HelpCopy(copy_all: true); + dict = dict._newTable; + } while (dict._newTable != null); + break; + } + + ref var e = ref entries[i]; + if (e.value == TOMBSTONE) + { + if (Interlocked.CompareExchange(ref e.value, TOMBPRIME, TOMBSTONE) == TOMBSTONE) + { + e.hash = SPECIAL_HASH_BITS; + e.key = default(TKeyStore); + Interlocked.Increment(ref dict._copyDone); + } + } + + } + + // got new requests while sweeping. revisit after next GC. + dict._topDict._sweeperInstance = this; + if (dict._topDict._sweepRequests != 0) + { + TryRearm(dict); + } + } + } + } +} \ No newline at end of file diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Counter/Counter32.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Counter/Counter32.cs new file mode 100644 index 000000000..039d242be --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Counter/Counter32.cs @@ -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 +{ + /// + /// Scalable 32bit counter that can be used from multiple threads. + /// + 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; + + /// + /// Returns the value of the counter at the time of the call. + /// + /// + /// The value may miss in-progress updates if the counter is being concurrently modified. + /// + 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; + } + } + + /// + /// Returns the approximate value of the counter at the time of the call. + /// + /// + /// EstimatedValue could be significantly cheaper to obtain, but may be slightly delayed. + /// + 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; + } + } + + /// + /// Increments the counter by 1. + /// + public void Increment() + { + int curCellCount = this.cellCount; + var drift = increment(ref GetCountRef(curCellCount)); + + if (drift != 0) + { + TryAddCell(curCellCount); + } + } + + /// + /// Decrements the counter by 1. + /// + public void Decrement() + { + int curCellCount = this.cellCount; + var drift = decrement(ref GetCountRef(curCellCount)); + + if (drift != 0) + { + TryAddCell(curCellCount); + } + } + + /// + /// Increments the counter by 'value'. + /// + 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); + //} + } + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Counter/Counter64.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Counter/Counter64.cs new file mode 100644 index 000000000..32e37ff20 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Counter/Counter64.cs @@ -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 +{ + /// + /// Scalable 64bit counter that can be used from multiple threads. + /// + 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; + + /// + /// Returns the value of the counter at the time of the call. + /// + /// + /// The value may miss in-progress updates if the counter is being concurrently modified. + /// + 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; + } + } + + /// + /// Returns the approximate value of the counter at the time of the call. + /// + /// + /// EstimatedValue could be significantly cheaper to obtain, but may be slightly delayed. + /// + 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; + } + } + + /// + /// Increments the counter by 1. + /// + public void Increment() + { + int curCellCount = this.cellCount; + var drift = increment(ref GetCountRef(curCellCount)); + + if (drift != 0) + { + TryAddCell(curCellCount); + } + } + + /// + /// Decrements the counter by 1. + /// + public void Decrement() + { + int curCellCount = this.cellCount; + var drift = decrement(ref GetCountRef(curCellCount)); + + if (drift != 0) + { + TryAddCell(curCellCount); + } + } + + /// + /// Increments the counter by 'value'. + /// + 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); + //} + } + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Counter/CounterBase.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Counter/CounterBase.cs new file mode 100644 index 000000000..9633ea5e3 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Counter/CounterBase.cs @@ -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 +{ + /// + /// Scalable counter base. + /// + 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); + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/NonBlockingDictionary.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/NonBlockingDictionary.cs new file mode 100644 index 000000000..6a44759da --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/NonBlockingDictionary.cs @@ -0,0 +1,1460 @@ +// Copyright (c) Vladimir Sadov. All rights reserved. +// +// This file is distributed under the MIT License. See LICENSE.md for details. + +#nullable disable +#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + +using System.Collections; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Collections.Concurrent; + +using static System.Collections.Concurrent.DictionaryImpl; + +namespace System.Collections.Concurrent +{ + /// + /// Represents a thread-safe and lock-free collection of keys and values. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// + /// All public and protected members of are thread-safe and may be used + /// concurrently from multiple threads. + /// + [DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))] + [DebuggerDisplay("Count = {Count}")] + public class NonBlockingDictionary : IDictionary, IDictionary, IReadOnlyDictionary where TKey : notnull + { + private readonly bool _valueIsValueType = typeof(TValue).IsValueType; + internal DictionaryImpl _table; + internal uint _lastResizeTickMillis; + internal object _sweeperInstance; + internal int _sweepRequests; + + /// The default capacity, i.e. the initial # of buckets. + /// + /// When choosing this value, we are making a trade-off between the size of a very small dictionary, + /// and the number of resizes when constructing a large dictionary. + /// + private const int DefaultCapacity = 0; + + /// Concurrency level is ignored. However it must be > 0. + private static int DefaultConcurrencyLevel => 1; + + /// + /// Initializes a new instance of the + /// class that is empty, has the default concurrency level, has the default initial capacity, and + /// uses the default comparer for the key type. + /// + public NonBlockingDictionary() : this(DefaultConcurrencyLevel, DefaultCapacity, null) { } + + /// + /// Initializes a new instance of the + /// class that is empty, has the specified concurrency level and capacity, and uses the default + /// comparer for the key type. + /// + /// The estimated number of threads that will update the + /// concurrently. + /// The initial number of elements that the can contain. + /// is less than 1. + /// is less than 0. + public NonBlockingDictionary(int concurrencyLevel, int capacity) : this(concurrencyLevel, capacity, null) { } + + /// + /// Initializes a new instance of the + /// class that contains elements copied from the specified , has the default concurrency + /// level, has the default initial capacity, and uses the default comparer for the key type. + /// + /// The whose elements are copied to the new . + /// is a null reference (Nothing in Visual Basic). + /// contains one or more duplicate keys. + public NonBlockingDictionary(IEnumerable> collection) : this(collection, null) { } + + /// + /// Initializes a new instance of the + /// class that is empty, has the specified concurrency level and capacity, and uses the specified + /// . + /// + /// The implementation to use when comparing keys. + public NonBlockingDictionary(IEqualityComparer? comparer) : this(DefaultConcurrencyLevel, DefaultCapacity, comparer) { } + + /// + /// Initializes a new instance of the + /// class that contains elements copied from the specified , has the default concurrency + /// level, has the default initial capacity, and uses the specified . + /// + /// The whose elements are copied to the new . + /// The implementation to use when comparing keys. + /// is a null reference (Nothing in Visual Basic). + public NonBlockingDictionary(IEnumerable> collection, IEqualityComparer? comparer) + : this(comparer) + { + if (collection is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(collection)); + } + + InitializeFromCollection(collection); + } + + /// + /// Initializes a new instance of the + /// class that contains elements copied from the specified , + /// has the specified concurrency level, has the specified initial capacity, and uses the specified + /// . + /// + /// + /// The estimated number of threads that will update the concurrently. + /// + /// The whose elements are copied to the new + /// . + /// The implementation to use when comparing keys. + /// is a null reference (Nothing in Visual Basic). + /// is less than 1. + /// contains one or more duplicate keys. + public NonBlockingDictionary(int concurrencyLevel, IEnumerable> collection, IEqualityComparer? comparer) + : this(concurrencyLevel, DefaultCapacity, comparer) + { + if (collection is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(collection)); + } + + InitializeFromCollection(collection); + } + + private void InitializeFromCollection(IEnumerable> collection) + { + foreach (KeyValuePair pair in collection) + { + if (pair.Key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + if (!this.TryAdd(pair.Key, pair.Value)) + { + throw new ArgumentException("duplicate keys"); + } + } + } + + /// + /// Initializes a new instance of the + /// class that is empty, has the specified concurrency level, has the specified initial capacity, and + /// uses the specified . + /// + /// The estimated number of threads that will update the concurrently. + /// The initial number of elements that the can contain. + /// The implementation to use when comparing keys. + /// is less than 1. -or- is less than 0. + public NonBlockingDictionary(int concurrencyLevel, int capacity, IEqualityComparer? comparer) + { + if (concurrencyLevel < 1) + { + throw new ArgumentOutOfRangeException(nameof(concurrencyLevel)); + } + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + // add some extra so that filled to capacity would be at 50% density + capacity = Math.Max(capacity, capacity * 2); + + if (!typeof(TKey).IsValueType) + { + _table = new DictionaryImplRef(capacity, this); + _table._keyComparer = comparer ?? EqualityComparer.Default; + return; + } + else + { + if (typeof(TKey) == typeof(int) || (typeof(TKey) == typeof(uint) && comparer == null)) + { + if (comparer == null) + { + _table = Unsafe.As>(new DictionaryImplIntNoComparer(capacity, Unsafe.As>(this))); + } + else + { + _table = Unsafe.As>(new DictionaryImplInt(capacity, Unsafe.As>(this))); + _table._keyComparer = comparer; + } + return; + } + + if (typeof(TKey) == typeof(long) || (typeof(TKey) == typeof(ulong) && comparer == null)) + { + if (comparer == null) + { + _table = Unsafe.As>(new DictionaryImplLongNoComparer(capacity, Unsafe.As>(this))); + } + else + { + _table = Unsafe.As>(new DictionaryImplLong(capacity, Unsafe.As>(this))); + _table._keyComparer = comparer; + } + return; + } + + if (typeof(TKey) == typeof(nint) || (typeof(TKey) == typeof(nuint) && comparer == null)) + { + if (comparer == null) + { + _table = Unsafe.As>(new DictionaryImplNintNoComparer(capacity, Unsafe.As>(this))); + } + else + { + _table = Unsafe.As>(new DictionaryImplNint(capacity, Unsafe.As>(this))); + _table._keyComparer = comparer; + } + return; + } + } + + _table = new DictionaryImplBoxed(capacity, this); + _table._keyComparer = comparer ?? EqualityComparer.Default; + } + + /// + /// Attempts to add the specified key and value to the . + /// + /// The key of the element to add. + /// The value of the element to add. The value can be a null reference (Nothing + /// in Visual Basic) for reference types. + /// + /// true if the key/value pair was added to the successfully; otherwise, false. + /// + /// is null reference (Nothing in Visual Basic). + /// The contains too many elements. + public bool TryAdd(TKey key, TValue value) + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + TValue oldVal = default; + return _table.PutIfMatch(key, value, ref oldVal, ValueMatch.NullOrDead); + } + + /// + /// Determines whether the contains the specified key. + /// + /// The key to locate in the . + /// true if the contains an element with the specified key; otherwise, false. + /// is a null reference (Nothing in Visual Basic). + public bool ContainsKey(TKey key) + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + object oldValObj = _table.TryGetValue(key); + Debug.Assert(!(oldValObj is Prime)); + + return oldValObj != null; + } + + /// + /// Attempts to remove and return the value with the specified key from the . + /// + /// The key of the element to remove and return. + /// + /// When this method returns, contains the object removed from the + /// or the default value of if the operation failed. + /// + /// true if an object was removed successfully; otherwise, false. + /// is a null reference (Nothing in Visual Basic). +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP || NETCOREAPP + public bool TryRemove(TKey key, [MaybeNullWhen(false)] out TValue value) +#else + public bool TryRemove(TKey key, out TValue value) +#endif + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + value = default; + return _table.RemoveIfMatch(key, ref value, ValueMatch.NotNullOrDead); + } + + /// Removes a key and value from the dictionary. + /// The representing the key and value to remove. + /// + /// true if the key and value represented by are successfully + /// found and removed; otherwise, false. + /// + /// + /// Both the specifed key and value must match the entry in the dictionary for it to be removed. + /// The key is compared using the dictionary's comparer (or the default comparer for + /// if no comparer was provided to the dictionary when it was constructed). The value is compared using the + /// default comparer for . + /// + /// + /// The property of is a null reference. + /// + public bool TryRemove(KeyValuePair item) + { + if (item.Key is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(item)); + } + + TValue oldVal = item.Value; + return _table.RemoveIfMatch(item.Key, ref oldVal, ValueMatch.OldValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private TValue FromObjectValue(object obj) + { + // regular value type + if (default(TValue) != null) + { + return Unsafe.As>(obj).Value; + } + + // null + if (obj == NULLVALUE) + { + return default(TValue); + } + + // ref type + if (!_valueIsValueType) + { + return Unsafe.As(ref obj); + } + + // nullable + return (TValue)obj; + } + + /// + /// Attempts to get the value associated with the specified key from the . + /// + /// The key of the value to get. + /// + /// When this method returns, contains the object from + /// the with the specified key or the default value of + /// , if the operation failed. + /// + /// true if the key was found in the ; otherwise, false. + /// is a null reference (Nothing in Visual Basic). +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) +#else + public bool TryGetValue(TKey key, out TValue value) +#endif + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + object oldValObj = _table.TryGetValue(key); + + Debug.Assert(!(oldValObj is Prime)); + + if (oldValObj != null) + { + value = FromObjectValue(oldValObj); + return true; + } + else + { + value = default(TValue); + return false; + } + } + + /// + /// Updates the value associated with to if the existing value is equal + /// to . + /// + /// The key whose value is compared with and + /// possibly replaced. + /// The value that replaces the value of the element with if the comparison results in equality. + /// The value that is compared to the value of the element with + /// . + /// + /// true if the value with was equal to and + /// replaced with ; otherwise, false. + /// + /// is a null reference. + public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue) + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + TValue oldVal = comparisonValue; + return _table.PutIfMatch(key, newValue, ref oldVal, ValueMatch.OldValue); + } + + /// + /// Removes all keys and values from the . + /// + public void Clear() => _table.Clear(); + + /// + /// Copies the elements of the to an array of type , + /// starting at the specified array index. + /// + /// + /// The one-dimensional array of type that is the destination of the elements copied from the . The array must have zero-based indexing. + /// + /// The zero-based index in at which copying begins. + /// is a null reference (Nothing in Visual Basic). + /// is less than 0. + /// + /// is equal to or greater than the length of the . -or- The number of + /// elements in the source is greater than the available space from to + /// the end of the destination . + /// + void ICollection>.CopyTo(KeyValuePair[] array, int index) + { + CopyToPairs(array, index); + } + + /// + /// Copies the key and value pairs stored in the to a + /// new array. + /// + /// A new array containing a snapshot of key and value pairs copied from the . + /// + public KeyValuePair[] ToArray() + { + var snapshot = _table.GetSnapshot(); + + int count = snapshot.Count; + if (count == 0) + { + return Array.Empty>(); + } + + var array = new KeyValuePair[count]; + int idx = 0; + while (snapshot.MoveNext() && idx < array.Length) + { + array[idx++] = snapshot.Current; + } + + if (idx != array.Length) + { + Array.Resize(ref array, idx); + } + + return array; + } + + /// Copy dictionary contents to an array. + private void CopyToPairs(KeyValuePair[] array, int index) + { + if (array is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(array)); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + var length = array.Length; + if (index > length) + { + throw new ArgumentException("array is too short"); + } + + foreach (var entry in this) + { + if ((uint)index < (uint)length) + { + array[index++] = entry; + } + else + { + throw new ArgumentException("array is too short"); + } + } + } + + /// Copy dictionary contents to an array. + private void CopyToEntries(DictionaryEntry[] array, int index) + { + if (array is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(array)); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + var length = array.Length; + if (index >= length) + { + throw new ArgumentException("array is too short"); + } + + foreach (var entry in this) + { + if ((uint)index < (uint)length) + { + array[index++] = new DictionaryEntry(entry.Key, entry.Value); + } + else + { + throw new ArgumentException("array is too short"); + } + } + } + + /// Copy dictionary contents to an array. + private void CopyToObjects(object[] array, int index) + { + if (array is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(array)); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + var length = array.Length; + if (index > length) + { + throw new ArgumentException("array is too short"); + } + + foreach (var entry in this) + { + if ((uint)index < (uint)length) + { + array[index++] = entry; + } + else + { + throw new ArgumentException("array is too short"); + } + } + } + + /// Returns an enumerator that iterates through the . + /// An enumerator for the . + /// + /// The enumerator returned from the dictionary is safe to use concurrently with + /// reads and writes to the dictionary, however it does not represent a moment-in-time snapshot + /// of the dictionary. The contents exposed through the enumerator may contain modifications + /// made to the dictionary after was called. + /// + public IEnumerator> GetEnumerator() + { + return new SnapshotEnumerator(_table.GetSnapshot()); + } + + /// Gets or sets the value associated with the specified key. + /// The key of the value to get or set. + /// + /// The value associated with the specified key. If the specified key is not found, a get operation throws a + /// , and a set operation creates a new element with the specified key. + /// + /// + /// is a null reference (Nothing in Visual Basic). + /// + /// + /// The property is retrieved and does not exist in the collection. + /// + public TValue this[TKey key] + { + get + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + object oldValObj = _table.TryGetValue(key); + Debug.Assert(oldValObj is not Prime); + + return oldValObj != null + ? FromObjectValue(oldValObj) + : ThrowKeyNotFoundException(); + } + set + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + TValue oldVal = default; + _table.PutIfMatch(key, value, ref oldVal, ValueMatch.Any); + } + } + + /// Throws a KeyNotFoundException. + /// Separate from ThrowHelper to avoid boxing at call site while reusing this generic instantiation. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP + [DoesNotReturn] +#endif + private static TValue ThrowKeyNotFoundException() => + throw new KeyNotFoundException(); + + /// + /// Gets the + /// that is used to determine equality of keys for the dictionary. + /// + /// + /// The generic interface implementation + /// that is used to determine equality of keys for the current + /// and to provide hash values for the keys. + /// + /// + /// requires an equality implementation to determine + /// whether keys are equal. You can specify an implementation of the + /// generic interface by using a constructor that accepts a comparer parameter; + /// if you do not specify one, the default generic equality comparer is used. + /// + public IEqualityComparer Comparer => _table._keyComparer ?? EqualityComparer.Default; + + /// + /// Gets the number of key/value pairs contained in the . + /// + /// The dictionary contains too many + /// elements. + /// The number of key/value pairs contained in the . + /// Count has snapshot semantics and represents the number of items in the + /// at the moment when Count was accessed. + public int Count => _table.Count; + + /// + /// Adds a key/value pair to the + /// if the key does not already exist. + /// + /// The key of the element to add. + /// The function used to generate a value for the key + /// is a null reference + /// (Nothing in Visual Basic). + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// The value for the key. This will be either the existing value for the key if the + /// key is already in the dictionary, or the new value for the key as returned by valueFactory + /// if the key was not in the dictionary. + public TValue GetOrAdd(TKey key, Func valueFactory) + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + if (valueFactory is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(valueFactory)); + } + + return _table.GetOrAdd(key, valueFactory); + } + + /// + /// Adds a key/value pair to the + /// if the key does not already exist. + /// + /// The key of the element to add. + /// The function used to generate a value for the key + /// An argument value to pass into . + /// is a null reference + /// (Nothing in Visual Basic). + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// The value for the key. This will be either the existing value for the key if the + /// key is already in the dictionary, or the new value for the key as returned by valueFactory + /// if the key was not in the dictionary. + public TValue GetOrAdd(TKey key, Func valueFactory, TArg factoryArgument) + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + if (valueFactory is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(valueFactory)); + } + + object oldValObj = _table.TryGetValue(key); + Debug.Assert(!(oldValObj is Prime)); + + if (oldValObj != null) + { + return FromObjectValue(oldValObj); + } + else + { + TValue newValue = valueFactory(key, factoryArgument); + TValue oldVal = default; + if (_table.PutIfMatch(key, newValue, ref oldVal, ValueMatch.NullOrDead)) + { + return newValue; + } + else + { + return oldVal; + } + } + } + + /// + /// Adds a key/value pair to the + /// if the key does not already exist. + /// + /// The key of the element to add. + /// the value to be added, if the key does not already exist + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// The value for the key. This will be either the existing value for the key if the + /// key is already in the dictionary, or the new value if the key was not in the dictionary. + public TValue GetOrAdd(TKey key, TValue value) + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + TValue oldVal = default; + if (_table.PutIfMatch(key, value, ref oldVal, ValueMatch.NullOrDead)) + { + return value; + } + + return oldVal; + } + + + /// + /// Adds a key/value pair to the if the key does not already + /// exist, or updates a key/value pair in the if the key + /// already exists. + /// + /// The key to be added or whose value should be updated + /// The function used to generate a value for an absent key + /// The function used to generate a new value for an existing key + /// based on the key's existing value + /// An argument to pass into and . + /// is a null reference + /// (Nothing in Visual Basic). + /// is a null reference + /// (Nothing in Visual Basic). + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// The new value for the key. This will be either be the result of addValueFactory (if the key was + /// absent) or the result of updateValueFactory (if the key was present). + public TValue AddOrUpdate( + TKey key, Func addValueFactory, Func updateValueFactory, TArg factoryArgument) + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + if (addValueFactory is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(addValueFactory)); + } + + if (updateValueFactory is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(updateValueFactory)); + } + + TValue tValue2; + while (true) + { + TValue tValue; + if (this.TryGetValue(key, out tValue)) + { + tValue2 = updateValueFactory(key, tValue, factoryArgument); + if (this.TryUpdate(key, tValue2, tValue)) + { + return tValue2; + } + } + else + { + tValue2 = addValueFactory(key, factoryArgument); + if (this.TryAdd(key, tValue2)) + { + return tValue2; + } + } + } + } + + /// + /// Adds a key/value pair to the if the key does not already + /// exist, or updates a key/value pair in the if the key + /// already exists. + /// + /// The key to be added or whose value should be updated + /// The function used to generate a value for an absent key + /// The function used to generate a new value for an existing key + /// based on the key's existing value + /// is a null reference + /// (Nothing in Visual Basic). + /// is a null reference + /// (Nothing in Visual Basic). + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// The new value for the key. This will be either the result of addValueFactory (if the key was + /// absent) or the result of updateValueFactory (if the key was present). + public TValue AddOrUpdate(TKey key, Func addValueFactory, Func updateValueFactory) + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + if (addValueFactory is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(addValueFactory)); + } + + if (updateValueFactory is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(updateValueFactory)); + } + + TValue tValue2; + while (true) + { + TValue tValue; + if (this.TryGetValue(key, out tValue)) + { + tValue2 = updateValueFactory(key, tValue); + if (this.TryUpdate(key, tValue2, tValue)) + { + break; + } + } + else + { + tValue2 = addValueFactory(key); + if (this.TryAdd(key, tValue2)) + { + break; + } + } + } + return tValue2; + } + + /// + /// Adds a key/value pair to the if the key does not already + /// exist, or updates a key/value pair in the if the key + /// already exists. + /// + /// The key to be added or whose value should be updated + /// The value to be added for an absent key + /// The function used to generate a new value for an existing key based on + /// the key's existing value + /// is a null reference + /// (Nothing in Visual Basic). + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// The new value for the key. This will be either the value of addValue (if the key was + /// absent) or the result of updateValueFactory (if the key was present). + public TValue AddOrUpdate(TKey key, TValue addValue, Func updateValueFactory) + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + if (updateValueFactory is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(updateValueFactory)); + } + + TValue tValue2; + while (true) + { + TValue tValue; + if (this.TryGetValue(key, out tValue)) + { + tValue2 = updateValueFactory(key, tValue); + if (this.TryUpdate(key, tValue2, tValue)) + { + return tValue2; + } + } + else if (this.TryAdd(key, addValue)) + { + return addValue; + } + } + } + + /// + /// Gets a value that indicates whether the is empty. + /// + /// true if the is empty; otherwise, + /// false. + public bool IsEmpty => _table.Count == 0; + #region IDictionary members + + /// + /// Adds the specified key and value to the . + /// + /// The object to use as the key of the element to add. + /// The object to use as the value of the element to add. + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// + /// An element with the same key already exists in the . + void IDictionary.Add(TKey key, TValue value) + { + if (!TryAdd(key, value)) + { + throw new ArgumentException("duplicate key"); + } + } + + /// + /// Removes the element with the specified key from the . + /// + /// The key of the element to remove. + /// true if the element is successfully remove; otherwise false. This method also returns + /// false if + /// was not found in the original . + /// + /// is a null reference + /// (Nothing in Visual Basic). + bool IDictionary.Remove(TKey key) => TryRemove(key, out _); + + /// + /// Gets a collection containing the keys in the . + /// + /// An containing the keys in the + /// . + public ICollection Keys => GetKeys(); + + /// + /// Gets an containing the keys of + /// the . + /// + /// An containing the keys of + /// the . + IEnumerable IReadOnlyDictionary.Keys => GetKeys(); + + /// + /// Gets a collection containing the values in the . + /// + /// An containing the values in + /// the + /// . + public ICollection Values => GetValues(); + + /// + /// Gets an containing the values + /// in the . + /// + /// An containing the + /// values in the . + IEnumerable IReadOnlyDictionary.Values => GetValues(); + #endregion + + #region ICollection> Members + + /// + /// Adds the specified value to the + /// with the specified key. + /// + /// The + /// structure representing the key and value to add to the . + /// The of is null. + /// The + /// contains too many elements. + /// An element with the same key already exists in the + /// + void ICollection>.Add(KeyValuePair keyValuePair) => ((IDictionary)this).Add(keyValuePair.Key, keyValuePair.Value); + + /// + /// Determines whether the + /// contains a specific key and value. + /// + /// The + /// structure to locate in the . + /// true if the is found in the ; otherwise, false. + bool ICollection>.Contains(KeyValuePair keyValuePair) + { + TValue value; + return TryGetValue(keyValuePair.Key, out value) && + EqualityComparer.Default.Equals(value, keyValuePair.Value); + } + + /// + /// Gets a value indicating whether the dictionary is read-only. + /// + /// true if the is + /// read-only; otherwise, false. For , this property always returns + /// false. + bool ICollection>.IsReadOnly => false; + + /// + /// Removes a key and value from the dictionary. + /// + /// The + /// structure representing the key and value to remove from the . + /// true if the key and value represented by is successfully + /// found and removed; otherwise, false. + /// The Key property of is a null reference (Nothing in Visual Basic). + bool ICollection>.Remove(KeyValuePair keyValuePair) => + TryRemove(keyValuePair); + + #endregion + + #region IEnumerable Members + + /// Returns an enumerator that iterates through the . + /// An enumerator for the . + /// + /// The enumerator returned from the dictionary is safe to use concurrently with + /// reads and writes to the dictionary, however it does not represent a moment-in-time snapshot + /// of the dictionary. The contents exposed through the enumerator may contain modifications + /// made to the dictionary after was called. + /// + IEnumerator IEnumerable.GetEnumerator() => ((NonBlockingDictionary)this).GetEnumerator(); + + #endregion + + #region IDictionary Members + + /// + /// Adds the specified key and value to the dictionary. + /// + /// The object to use as the key. + /// The object to use as the value. + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// + /// is of a type that is not assignable to the key type of the . -or- + /// is of a type that is not assignable to , + /// the type of values in the . + /// -or- A value with the same key already exists in the . + /// + void IDictionary.Add(object key, object? value) + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + if (!(key is TKey)) + { + throw new ArgumentException("type of key is incorrect"); + } + + ThrowIfInvalidObjectValue(value); + + ((IDictionary)this).Add((TKey)key, (TValue)value!); + } + + /// + /// Gets whether the contains an + /// element with the specified key. + /// + /// The key to locate in the . + /// true if the contains + /// an element with the specified key; otherwise, false. + /// is a null reference + /// (Nothing in Visual Basic). + bool IDictionary.Contains(object key) + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + return key is TKey tkey && ContainsKey(tkey); + } + + /// Provides an for the + /// . + /// An for the . + IDictionaryEnumerator IDictionary.GetEnumerator() => new SnapshotIDictionaryEnumerator(_table.GetSnapshot()); + + /// + /// Gets a value indicating whether the has a fixed size. + /// + /// true if the has a + /// fixed size; otherwise, false. For , this property always + /// returns false. + bool IDictionary.IsFixedSize => false; + + /// + /// Gets a value indicating whether the is read-only. + /// + /// true if the is + /// read-only; otherwise, false. For , this property always + /// returns false. + bool IDictionary.IsReadOnly => false; + + /// + /// Gets an containing the keys of the . + /// + /// An containing the keys of the . + ICollection IDictionary.Keys => GetKeys(); + + /// + /// Removes the element with the specified key from the . + /// + /// The key of the element to remove. + /// is a null reference + /// (Nothing in Visual Basic). + void IDictionary.Remove(object key) + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + if (key is TKey tkey) + { + TryRemove(tkey, out _); + } + } + + /// + /// Gets an containing the values in the . + /// + /// An containing the values in the . + ICollection IDictionary.Values => GetValues(); + + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + /// The value associated with the specified key, or a null reference (Nothing in Visual Basic) + /// if is not in the dictionary or is of a type that is + /// not assignable to the key type of the . + /// is a null reference + /// (Nothing in Visual Basic). + /// + /// A value is being assigned, and is of a type that is not assignable to the + /// key type of the . -or- A value is being + /// assigned, and is of a type that is not assignable to the value type + /// of the + /// + object? IDictionary.this[object key] + { + get + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + if (key is TKey tkey && TryGetValue(tkey, out TValue? value)) + { + return value; + } + + return null; + } + set + { + if (key is null) + { + ThrowHelper.ThrowKeyNullException(); + } + + if (!(key is TKey)) + { + throw new ArgumentException("type of key is incorrect"); + } + + ThrowIfInvalidObjectValue(value); + + ((NonBlockingDictionary)this)[(TKey)key] = (TValue)value!; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ThrowIfInvalidObjectValue(object? value) + { + if (value != null) + { + if (!(value is TValue)) + { + ThrowHelper.ThrowValueNullException(); + } + } + else if (default(TValue) != null) + { + ThrowHelper.ThrowValueNullException(); + } + } + + #endregion + + #region ICollection Members + + /// + /// Copies the elements of the to an array, starting + /// at the specified array index. + /// + /// The one-dimensional array that is the destination of the elements copied from + /// the . The array must have zero-based + /// indexing. + /// The zero-based index in at which copying + /// begins. + /// is a null reference + /// (Nothing in Visual Basic). + /// is less than + /// 0. + /// is equal to or greater than + /// the length of the . -or- The number of elements in the source + /// is greater than the available space from to the end of the destination + /// . + void ICollection.CopyTo(Array array, int index) + { + if (array is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(array)); + } + + // To be consistent with the behavior of ICollection.CopyTo() in Dictionary, + // we recognize three types of target arrays: + // - an array of KeyValuePair structs + // - an array of DictionaryEntry structs + // - an array of objects + + if (array is KeyValuePair[] pairs) + { + CopyToPairs(pairs, index); + return; + } + + if (array is DictionaryEntry[] entries) + { + CopyToEntries(entries, index); + return; + } + + if (array is object[] objects) + { + CopyToObjects(objects, index); + return; + } + + throw new ArgumentException("array type is incorrect", nameof(array)); + } + + /// + /// Gets a value indicating whether access to the is + /// synchronized with the SyncRoot. + /// + /// true if access to the is synchronized + /// (thread safe); otherwise, false. For , this property always + /// returns false. + bool ICollection.IsSynchronized => false; + + /// + /// Gets an object that can be used to synchronize access to the . This property is not supported. + /// + /// The SyncRoot property is not supported. + object ICollection.SyncRoot => throw new NotSupportedException("SyncRoot not supported"); + + #endregion + + + /// + /// Gets a collection containing the keys in the dictionary. + /// + private ReadOnlyCollection GetKeys() + { + var keys = new List(Count); + foreach (var kv in this) + { + keys.Add(kv.Key); + } + + return new ReadOnlyCollection(keys); + } + + /// + /// Gets a collection containing the values in the dictionary. + /// + private ReadOnlyCollection GetValues() + { + var values = new List(Count); + foreach (var kv in this) + { + values.Add(kv.Value); + } + + return new ReadOnlyCollection(values); + } + + internal class SnapshotEnumerator : IEnumerator> + { + private DictionaryImpl.Snapshot _snapshot; + public SnapshotEnumerator(DictionaryImpl.Snapshot snapshot) + { + _snapshot = snapshot; + } + + public KeyValuePair Current => _snapshot.Current; + object IEnumerator.Current => _snapshot.Current; + + public bool MoveNext() => _snapshot.MoveNext(); + public void Reset() => _snapshot.Reset(); + public void Dispose() { } + } + + internal class SnapshotIDictionaryEnumerator : IDictionaryEnumerator + { + private DictionaryImpl.Snapshot _snapshot; + public SnapshotIDictionaryEnumerator(DictionaryImpl.Snapshot snapshot) + { + _snapshot = snapshot; + } + + public DictionaryEntry Entry => _snapshot.Entry; + object IEnumerator.Current => _snapshot.Entry; + + public object Key => _snapshot.Current.Key; + public object Value => _snapshot.Current.Value; + + public bool MoveNext() => _snapshot.MoveNext(); + public void Reset() => _snapshot.Reset(); + public void Dispose() { } + } + } + + internal sealed class IDictionaryDebugView where TKey : notnull + { + private readonly IDictionary _dictionary; + + public IDictionaryDebugView(IDictionary dictionary) + { + if (dictionary is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(dictionary)); + } + + _dictionary = dictionary; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public KeyValuePair[] Items + { + get + { + var items = new KeyValuePair[_dictionary.Count]; + _dictionary.CopyTo(items, 0); + return items; + } + } + } + + internal static class ThrowHelper + { +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP + [DoesNotReturn] +#endif + internal static void ThrowKeyNullException() => ThrowArgumentNullException("key"); + +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP + [DoesNotReturn] +#endif + internal static void ThrowArgumentNullException(string name) => throw new ArgumentNullException(name); + +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP + [DoesNotReturn] +#endif + internal static void ThrowArgumentNullException(string name, string message) => throw new ArgumentNullException(name, message); + +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP + [DoesNotReturn] +#endif + internal static void ThrowValueNullException() => throw new ArgumentException("value is null"); + +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP + [DoesNotReturn] +#endif + internal static void ThrowOutOfMemoryException() => throw new OutOfMemoryException(); + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Util.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Util.cs new file mode 100644 index 000000000..cefa3da19 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/Util.cs @@ -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; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPool.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPool.cs index 257685363..6e5f9869b 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPool.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPool.cs @@ -44,7 +44,7 @@ public class ObjectPool : DisposeBase, IPool where T : notnull private readonly ConcurrentQueue _free2 = new(); /// 借出去的放在这 - private readonly ConcurrentDictionary _busy = new(); + private readonly NonBlockingDictionary _busy = new(); //private readonly Object SyncRoot = new(); #endregion diff --git a/src/Admin/ThingsGateway.NewLife.X/Common/ExpiringDictionary.cs b/src/Admin/ThingsGateway.NewLife.X/Common/ExpiringDictionary.cs index 732227dcb..5e3a70b8c 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Common/ExpiringDictionary.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Common/ExpiringDictionary.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using System.Runtime.CompilerServices; using ThingsGateway.NewLife.Threading; namespace ThingsGateway.NewLife; @@ -52,7 +51,7 @@ public class ExpiringDictionary : IDisposable return rs; } } - private ConcurrentDictionary _dict; + private NonBlockingDictionary _dict; private readonly TimerX _cleanupTimer; private int defaultExpire = 60; @@ -61,7 +60,7 @@ public class ExpiringDictionary : IDisposable { defaultExpire = expire; this.comparer = comparer; - _dict = new ConcurrentDictionary(comparer); + _dict = new NonBlockingDictionary(comparer); _cleanupTimer = new TimerX(TimerClear, null, 10000, 10000) { Async = true }; } diff --git a/src/Admin/ThingsGateway.NewLife.X/Common/FastMapper.cs b/src/Admin/ThingsGateway.NewLife.X/Common/FastMapper.cs index 9b4fe8543..5aae8adf8 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Common/FastMapper.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Common/FastMapper.cs @@ -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 source, FastMapperOption option = null) diff --git a/src/Admin/ThingsGateway.NewLife.X/Configuration/CompositeConfigProvider.cs b/src/Admin/ThingsGateway.NewLife.X/Configuration/CompositeConfigProvider.cs index 6fd1d2424..e6254fd81 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Configuration/CompositeConfigProvider.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Configuration/CompositeConfigProvider.cs @@ -168,8 +168,8 @@ public class CompositeConfigProvider : IConfigProvider #endregion #region 绑定 - private readonly ConcurrentDictionary _models = []; - private readonly ConcurrentDictionary _models2 = []; + private readonly NonBlockingDictionary _models = []; + private readonly NonBlockingDictionary _models2 = []; /// 绑定模型,使能热更新,配置存储数据改变时同步修改模型属性 /// 模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例 /// 模型实例 diff --git a/src/Admin/ThingsGateway.NewLife.X/Extension/DictionaryExtensions.cs b/src/Admin/ThingsGateway.NewLife.X/Extension/DictionaryExtensions.cs index a37151f65..9b4b78a1d 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Extension/DictionaryExtensions.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Extension/DictionaryExtensions.cs @@ -11,7 +11,7 @@ public static class DictionaryExtensions /// /// /// - public static Boolean Remove(this ConcurrentDictionary dict, TKey key) where TKey : notnull => dict.TryRemove(key, out _); + public static Boolean Remove(this NonBlockingDictionary dict, TKey key) where TKey : notnull => dict.TryRemove(key, out _); #if !NET6_0_OR_GREATER public static bool TryAdd(this IDictionary pairs, TKey key, TValue value) @@ -77,7 +77,7 @@ public static class DictionaryExtensions /// /// 批量出队 /// - public static List ToListWithDequeue(this ConcurrentDictionary values, int maxCount = 0) + public static List ToListWithDequeue(this NonBlockingDictionary values, int maxCount = 0) { if (maxCount <= 0) { @@ -105,7 +105,7 @@ public static class DictionaryExtensions /// /// 批量出队 /// - public static Dictionary ToDictWithDequeue(this ConcurrentDictionary values, int maxCount = 0) + public static Dictionary ToDictWithDequeue(this NonBlockingDictionary values, int maxCount = 0) { if (maxCount <= 0) { @@ -135,7 +135,7 @@ public static class DictionaryExtensions /// /// 批量出队 /// - public static IEnumerable> ToIEnumerableKVWithDequeue(this ConcurrentDictionary values, int maxCount = 0) + public static IEnumerable> ToIEnumerableKVWithDequeue(this NonBlockingDictionary values, int maxCount = 0) { if (values.IsEmpty) yield break; diff --git a/src/Admin/ThingsGateway.NewLife.X/Extension/LinqExtensions.cs b/src/Admin/ThingsGateway.NewLife.X/Extension/LinqExtensions.cs index e7ae21e10..ef1c508c9 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Extension/LinqExtensions.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Extension/LinqExtensions.cs @@ -101,7 +101,7 @@ public static class LinqExtensions /// /// 从并发字典中删除 /// - public static bool Remove(this ConcurrentDictionary dict, TKey key) where TKey : notnull + public static bool Remove(this NonBlockingDictionary dict, TKey key) where TKey : notnull { return dict.TryRemove(key, out TValue? _); } diff --git a/src/Admin/ThingsGateway.NewLife.X/Logger/ConsoleLog.cs b/src/Admin/ThingsGateway.NewLife.X/Logger/ConsoleLog.cs index 4dc856038..f6ea12441 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Logger/ConsoleLog.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Logger/ConsoleLog.cs @@ -97,7 +97,7 @@ public class ConsoleLog : Logger } } - static readonly ConcurrentDictionary dic = new(); + static readonly NonBlockingDictionary 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 ]; diff --git a/src/Admin/ThingsGateway.NewLife.X/Logger/ITracer.cs b/src/Admin/ThingsGateway.NewLife.X/Logger/ITracer.cs index e8109b2dd..fafdc124f 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Logger/ITracer.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Logger/ITracer.cs @@ -109,7 +109,7 @@ public class DefaultTracer : DisposeBase, ITracer, ILogFeature public IPool SpanPool => _SpanPool ??= new MySpanPool(); /// Span构建器集合 - protected ConcurrentDictionary _builders = new(); + protected NonBlockingDictionary _builders = new(); /// 采样定时器 protected TimerX? _timer; @@ -292,7 +292,7 @@ public class DefaultTracer : DisposeBase, ITracer, ILogFeature var bs = _builders; if (bs.IsEmpty) return []; - _builders = new ConcurrentDictionary(); + _builders = new NonBlockingDictionary(); var bs2 = bs.Values.Where(e => e.Total > 0).ToArray(); diff --git a/src/Admin/ThingsGateway.NewLife.X/Logger/TextFileLog.cs b/src/Admin/ThingsGateway.NewLife.X/Logger/TextFileLog.cs index 8b16b3656..30c8c4bfb 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Logger/TextFileLog.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Logger/TextFileLog.cs @@ -64,7 +64,7 @@ public class TextFileLog : Logger, IDisposable _Timer = new TimerX(DoWriteAndClose, null, 0_000, 5_000) { Async = true }; } - private static readonly ConcurrentDictionary cache = new(StringComparer.OrdinalIgnoreCase); + private static readonly NonBlockingDictionary cache = new(StringComparer.OrdinalIgnoreCase); /// 每个目录的日志实例应该只有一个,所以采用静态创建 /// 日志目录或日志文件路径 /// @@ -257,7 +257,7 @@ public class TextFileLog : Logger, IDisposable { // 删除最旧的文件 var retain = fis.Length - Backups; - fis = fis.OrderBy(e => e.CreationTime).Take(retain).ToArray(); + fis = fis.OrderBy(e => e.LastWriteTimeUtc).Take(retain).ToArray(); foreach (var item in fis) { OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length); diff --git a/src/Admin/ThingsGateway.NewLife.X/Messaging/IEventBus.cs b/src/Admin/ThingsGateway.NewLife.X/Messaging/IEventBus.cs index 8736e901e..603747739 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Messaging/IEventBus.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Messaging/IEventBus.cs @@ -59,7 +59,7 @@ public interface IEventHandler /// public class EventBus : DisposeBase, IEventBus { - private readonly ConcurrentDictionary> _handlers = []; + private readonly NonBlockingDictionary> _handlers = []; /// 已订阅的事件处理器集合 public IDictionary> Handlers => _handlers; diff --git a/src/Admin/ThingsGateway.NewLife.X/Model/DeferredQueue.cs b/src/Admin/ThingsGateway.NewLife.X/Model/DeferredQueue.cs index 132815012..76d46a91a 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Model/DeferredQueue.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Model/DeferredQueue.cs @@ -20,9 +20,9 @@ public class DeferredQueue : DisposeBase /// 名称 public String Name { get; set; } - private volatile ConcurrentDictionary _Entities = new(); + private volatile NonBlockingDictionary _Entities = new(); /// 实体字典 - public ConcurrentDictionary Entities => _Entities; + public NonBlockingDictionary Entities => _Entities; /// 跟踪数。达到该值时输出跟踪日志,默认1000 public Int32 TraceCount { get; set; } = 1000; @@ -206,7 +206,7 @@ public class DeferredQueue : DisposeBase var es = _Entities; if (es.IsEmpty) return; - _Entities = new ConcurrentDictionary(); + _Entities = new NonBlockingDictionary(); var times = _Times; Interlocked.Add(ref _count, -es.Count); diff --git a/src/Admin/ThingsGateway.NewLife.X/Model/IServiceScope.cs b/src/Admin/ThingsGateway.NewLife.X/Model/IServiceScope.cs index b673dfb28..e122a7fe6 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Model/IServiceScope.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Model/IServiceScope.cs @@ -18,7 +18,7 @@ class MyServiceScope : IServiceScope, IServiceProvider public IServiceProvider ServiceProvider => this; - private readonly ConcurrentDictionary _cache = new(); + private readonly NonBlockingDictionary _cache = new(); public void Dispose() { diff --git a/src/Admin/ThingsGateway.NewLife.X/Net/IDnsResolver.cs b/src/Admin/ThingsGateway.NewLife.X/Net/IDnsResolver.cs index 9c5faa94b..51d244af7 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Net/IDnsResolver.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Net/IDnsResolver.cs @@ -23,7 +23,7 @@ public class DnsResolver : IDnsResolver /// 缓存超时时间 public TimeSpan Expire { set; get; } = TimeSpan.FromMinutes(5); - private readonly ConcurrentDictionary _cache = new(); + private readonly NonBlockingDictionary _cache = new(); /// 解析域名 /// diff --git a/src/Admin/ThingsGateway.NewLife.X/Net/NetServer.cs b/src/Admin/ThingsGateway.NewLife.X/Net/NetServer.cs index d51193407..d80351643 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Net/NetServer.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Net/NetServer.cs @@ -146,7 +146,7 @@ public class NetServer : DisposeBase, IServer, IExtend, ILogFeature /// public IServiceProvider? ServiceProvider { get; set; } - private ConcurrentDictionary? _items; + private NonBlockingDictionary? _items; /// 数据项 public IDictionary Items => _items ??= new(); @@ -486,7 +486,7 @@ public class NetServer : DisposeBase, IServer, IExtend, ILogFeature #endregion #region 会话 - private readonly ConcurrentDictionary _Sessions = new(); + private readonly NonBlockingDictionary _Sessions = new(); /// 会话集合。用自增的数字ID作为标识,业务应用自己维持ID与业务主键的对应关系。 public IDictionary Sessions => _Sessions; diff --git a/src/Admin/ThingsGateway.NewLife.X/Net/SessionBase.cs b/src/Admin/ThingsGateway.NewLife.X/Net/SessionBase.cs index ce0d35c08..1a8884d78 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Net/SessionBase.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Net/SessionBase.cs @@ -853,7 +853,7 @@ public abstract class SessionBase : DisposeBase, ISocketClient, ITransport, ILog #endregion 异常处理 #region 扩展接口 - private ConcurrentDictionary? _items; + private NonBlockingDictionary? _items; /// 数据项 public IDictionary Items => _items ??= new(); diff --git a/src/Admin/ThingsGateway.NewLife.X/Net/SessionCollection.cs b/src/Admin/ThingsGateway.NewLife.X/Net/SessionCollection.cs index 0249c7715..da6e6cd44 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Net/SessionCollection.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Net/SessionCollection.cs @@ -10,7 +10,7 @@ namespace ThingsGateway.NewLife.Net; internal class SessionCollection : DisposeBase, IDictionary { #region 属性 - private readonly ConcurrentDictionary _dic = new(); + private readonly NonBlockingDictionary _dic = new(); /// 服务端 public ISocketServer Server { get; private set; } diff --git a/src/Admin/ThingsGateway.NewLife.X/Net/UdpSession.cs b/src/Admin/ThingsGateway.NewLife.X/Net/UdpSession.cs index 7a49014f6..aa80d7990 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Net/UdpSession.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Net/UdpSession.cs @@ -400,7 +400,7 @@ public class UdpSession : DisposeBase, ISocketSession, ITransport, ILogFeature #endregion #region 扩展接口 - private ConcurrentDictionary? _items; + private NonBlockingDictionary? _items; /// 数据项 public IDictionary Items => _items ??= new(); diff --git a/src/Admin/ThingsGateway.NewLife.X/Redis/FullRedis.cs b/src/Admin/ThingsGateway.NewLife.X/Redis/FullRedis.cs index f8ac25fed..0961ff9a7 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Redis/FullRedis.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Redis/FullRedis.cs @@ -212,7 +212,7 @@ public class FullRedis : Redis } } - private ConcurrentDictionary> _pools = new(); + private NonBlockingDictionary> _pools = new(); /// 获取指定节点的连接池 /// /// diff --git a/src/Admin/ThingsGateway.NewLife.X/Redis/RedisClient.cs b/src/Admin/ThingsGateway.NewLife.X/Redis/RedisClient.cs index a381e68f4..6f1530fbd 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Redis/RedisClient.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Redis/RedisClient.cs @@ -1106,10 +1106,10 @@ public class RedisClient : DisposeBase #endregion #region 辅助 - private static readonly ConcurrentDictionary _cache0 = new(); - private static readonly ConcurrentDictionary _cache1 = new(); - private static readonly ConcurrentDictionary _cache2 = new(); - private static readonly ConcurrentDictionary _cache3 = new(); + private static readonly NonBlockingDictionary _cache0 = new(); + private static readonly NonBlockingDictionary _cache1 = new(); + private static readonly NonBlockingDictionary _cache2 = new(); + private static readonly NonBlockingDictionary _cache3 = new(); /// 获取命令对应的字节数组,全局缓存 /// /// diff --git a/src/Admin/ThingsGateway.NewLife.X/Reflection/AssemblyX.cs b/src/Admin/ThingsGateway.NewLife.X/Reflection/AssemblyX.cs index 9110249ea..1ede0e55a 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Reflection/AssemblyX.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Reflection/AssemblyX.cs @@ -94,7 +94,7 @@ public class AssemblyX #region 构造 private AssemblyX(Assembly asm) => Asm = asm; - private static readonly ConcurrentDictionary cache = new(); + private static readonly NonBlockingDictionary cache = new(); /// 创建程序集辅助对象 /// /// @@ -234,7 +234,7 @@ public class AssemblyX #endregion #region 方法 - private readonly ConcurrentDictionary typeCache2 = new(); + private readonly NonBlockingDictionary typeCache2 = new(); /// 从程序集中查找指定名称的类型 /// /// @@ -268,7 +268,7 @@ public class AssemblyX #endregion #region 插件 - private readonly ConcurrentDictionary> _plugins = new(); + private readonly NonBlockingDictionary> _plugins = new(); /// 查找插件,带缓存 /// 类型 /// diff --git a/src/Admin/ThingsGateway.NewLife.X/Reflection/AttributeX.cs b/src/Admin/ThingsGateway.NewLife.X/Reflection/AttributeX.cs index c19cd43e0..a900159f2 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Reflection/AttributeX.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Reflection/AttributeX.cs @@ -10,7 +10,7 @@ namespace ThingsGateway.NewLife; public static class AttributeX { #region 静态方法 - private static readonly ConcurrentDictionary _asmCache = new(); + private static readonly NonBlockingDictionary _asmCache = new(); /// 获取自定义属性,带有缓存功能,避免因.Net内部GetCustomAttributes没有缓存而带来的损耗 /// diff --git a/src/Admin/ThingsGateway.NewLife.X/Reflection/IReflect.cs b/src/Admin/ThingsGateway.NewLife.X/Reflection/IReflect.cs index e74c8cd6d..210db13be 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Reflection/IReflect.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Reflection/IReflect.cs @@ -317,8 +317,8 @@ public class DefaultReflect : IReflect #endregion #region 反射获取 字段/属性 - private readonly ConcurrentDictionary> _cache1 = new(); - private readonly ConcurrentDictionary> _cache2 = new(); + private readonly NonBlockingDictionary> _cache1 = new(); + private readonly NonBlockingDictionary> _cache2 = new(); /// 获取字段 /// /// @@ -353,8 +353,8 @@ public class DefaultReflect : IReflect return list; } - private readonly ConcurrentDictionary> _cache3 = new(); - private readonly ConcurrentDictionary> _cache4 = new(); + private readonly NonBlockingDictionary> _cache3 = new(); + private readonly NonBlockingDictionary> _cache4 = new(); /// 获取属性 /// /// @@ -805,7 +805,7 @@ public class DefaultReflect : IReflect #endregion #region 插件 - //private readonly ConcurrentDictionary> _as_cache = new ConcurrentDictionary>(); + //private readonly NonBlockingDictionary> _as_cache = new NonBlockingDictionary>(); /// 是否子类 /// /// @@ -832,7 +832,7 @@ public class DefaultReflect : IReflect //var key = $"{type.FullName}_{baseType.FullName}"; //if (!_as_cache.TryGetValue(type, out var dic)) //{ - // dic = new ConcurrentDictionary(); + // dic = new NonBlockingDictionary(); // _as_cache.TryAdd(type, dic); //} diff --git a/src/Admin/ThingsGateway.NewLife.X/Reflection/ScriptEngine.cs b/src/Admin/ThingsGateway.NewLife.X/Reflection/ScriptEngine.cs index 079f84714..51251f56c 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Reflection/ScriptEngine.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Reflection/ScriptEngine.cs @@ -111,7 +111,7 @@ public class ScriptEngine IsExpression = isExpression; } - static readonly ConcurrentDictionary _cache = new(StringComparer.OrdinalIgnoreCase); + static readonly NonBlockingDictionary _cache = new(StringComparer.OrdinalIgnoreCase); /// 为指定代码片段创建脚本引擎实例。采用缓存,避免同一脚本重复创建引擎。 /// 代码片段 /// 是否表达式,表达式将编译成为一个Main方法 diff --git a/src/Admin/ThingsGateway.NewLife.X/Serialization/Binary/BinaryComposite.cs b/src/Admin/ThingsGateway.NewLife.X/Serialization/Binary/BinaryComposite.cs index 4da6a7d8a..936898997 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Serialization/Binary/BinaryComposite.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Serialization/Binary/BinaryComposite.cs @@ -243,7 +243,7 @@ public class BinaryComposite : BinaryHandlerBase throw new NotSupportedException(); } - private static readonly ConcurrentDictionary _cache = new(); + private static readonly NonBlockingDictionary _cache = new(); private static Boolean TryGetAccessor(MemberInfo member, [NotNullWhen(true)] out IMemberAccessor? acc) { if (_cache.TryGetValue(member, out acc)) return acc != null; diff --git a/src/Admin/ThingsGateway.NewLife.X/Serialization/Json/IJsonHost.cs b/src/Admin/ThingsGateway.NewLife.X/Serialization/Json/IJsonHost.cs index 8232d8aa7..e57792109 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Serialization/Json/IJsonHost.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Serialization/Json/IJsonHost.cs @@ -323,7 +323,7 @@ public class SystemJson : IJsonHost #region IJsonHost 成员 - private static readonly ConcurrentDictionary _optionsCache = new(); + private static readonly NonBlockingDictionary _optionsCache = new(); private static JsonSerializerOptions GetOptions(JsonSerializerOptions jsonSerializerOptions, bool indented, bool nullValue, bool camelCase) { diff --git a/src/Admin/ThingsGateway.NewLife.X/Serialization/SerialHelper.cs b/src/Admin/ThingsGateway.NewLife.X/Serialization/SerialHelper.cs index ed658f04e..c0fb39010 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Serialization/SerialHelper.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Serialization/SerialHelper.cs @@ -11,7 +11,7 @@ namespace ThingsGateway.NewLife.Serialization; /// 序列化助手 public static class SerialHelper { - private static readonly ConcurrentDictionary _cache = new(); + private static readonly NonBlockingDictionary _cache = new(); /// 获取序列化名称 /// /// diff --git a/src/Admin/ThingsGateway.SqlSugar/Sugar/IntegrationServices/SerializeService.cs b/src/Admin/ThingsGateway.SqlSugar/Sugar/IntegrationServices/SerializeService.cs index 84ae21c81..692f926be 100644 --- a/src/Admin/ThingsGateway.SqlSugar/Sugar/IntegrationServices/SerializeService.cs +++ b/src/Admin/ThingsGateway.SqlSugar/Sugar/IntegrationServices/SerializeService.cs @@ -31,7 +31,7 @@ namespace ThingsGateway.SqlSugar NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, }; - private static readonly ConcurrentDictionary _typeInfoCache = new(); + private static readonly NonBlockingDictionary _typeInfoCache = new(); static SerializeService() { diff --git a/src/Admin/ThingsGateway.SqlSugar/Sugar/Utilities/CallContextAsync.cs b/src/Admin/ThingsGateway.SqlSugar/Sugar/Utilities/CallContextAsync.cs index 385928aa8..d85a9a3c2 100644 --- a/src/Admin/ThingsGateway.SqlSugar/Sugar/Utilities/CallContextAsync.cs +++ b/src/Admin/ThingsGateway.SqlSugar/Sugar/Utilities/CallContextAsync.cs @@ -4,7 +4,7 @@ namespace ThingsGateway.SqlSugar { public static class CallContextAsync { - static ConcurrentDictionary> state = new ConcurrentDictionary>(); + static NonBlockingDictionary> state = new NonBlockingDictionary>(); public static void SetData(string name, T data) => state.GetOrAdd(name, _ => new AsyncLocal()).Value = data; public static T GetData(string name) => @@ -13,7 +13,7 @@ namespace ThingsGateway.SqlSugar public static class CallContextThread { - static ConcurrentDictionary> state = new ConcurrentDictionary>(); + static NonBlockingDictionary> state = new NonBlockingDictionary>(); public static void SetData(string name, T data) => state.GetOrAdd(name, _ => new ThreadLocal()).Value = data; public static T GetData(string name) => diff --git a/src/Admin/ThingsGateway.SqlSugar/Sugar/Utilities/FastCopy.cs b/src/Admin/ThingsGateway.SqlSugar/Sugar/Utilities/FastCopy.cs index 2836bb854..c5f43e224 100644 --- a/src/Admin/ThingsGateway.SqlSugar/Sugar/Utilities/FastCopy.cs +++ b/src/Admin/ThingsGateway.SqlSugar/Sugar/Utilities/FastCopy.cs @@ -8,7 +8,7 @@ namespace ThingsGateway.SqlSugar { internal static class FastCopy { - static ConcurrentDictionary copiers = new ConcurrentDictionary(); + static NonBlockingDictionary copiers = new NonBlockingDictionary(); /// /// 复制两个对象同名属性值 diff --git a/src/Admin/ThingsGateway.SqlSugar/Sugar/Utilities/PropertyCallAdapterProvider.cs b/src/Admin/ThingsGateway.SqlSugar/Sugar/Utilities/PropertyCallAdapterProvider.cs index cf4b555bf..bd596f440 100644 --- a/src/Admin/ThingsGateway.SqlSugar/Sugar/Utilities/PropertyCallAdapterProvider.cs +++ b/src/Admin/ThingsGateway.SqlSugar/Sugar/Utilities/PropertyCallAdapterProvider.cs @@ -22,8 +22,8 @@ namespace ThingsGateway.SqlSugar } public static class PropertyCallAdapterProvider { - private static readonly System.Collections.Concurrent.ConcurrentDictionary> _instances = - new System.Collections.Concurrent.ConcurrentDictionary>(); + private static readonly System.Collections.Concurrent.NonBlockingDictionary> _instances = + new System.Collections.Concurrent.NonBlockingDictionary>(); public static IPropertyCallAdapter GetInstance(string forPropertyName) { diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 243faf8ff..f5dabf8b1 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,9 +1,9 @@ - 10.11.105 - 10.11.105 - 10.11.105 + 10.11.107 + 10.11.107 + 10.11.107 10.11.6 10.11.6 8.0.21 diff --git a/src/Foundation/ThingsGateway.Foundation/Channel/DDP/DDPUdpSessionChannel.cs b/src/Foundation/ThingsGateway.Foundation/Channel/DDP/DDPUdpSessionChannel.cs index c4af36567..0ae5ff376 100644 --- a/src/Foundation/ThingsGateway.Foundation/Channel/DDP/DDPUdpSessionChannel.cs +++ b/src/Foundation/ThingsGateway.Foundation/Channel/DDP/DDPUdpSessionChannel.cs @@ -64,7 +64,7 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe public EndPoint DefaultEndpoint => RemoteIPHost?.EndPoint; - ConcurrentDictionary WaitLocks { get; } = new(); + NonBlockingDictionary WaitLocks { get; } = new(); public override WaitLock GetLock(string key) { diff --git a/src/Foundation/ThingsGateway.Foundation/Channel/DDP/InternalConcurrentDictionary.cs b/src/Foundation/ThingsGateway.Foundation/Channel/DDP/InternalConcurrentDictionary.cs index 1dc2f928c..8593bbd13 100644 --- a/src/Foundation/ThingsGateway.Foundation/Channel/DDP/InternalConcurrentDictionary.cs +++ b/src/Foundation/ThingsGateway.Foundation/Channel/DDP/InternalConcurrentDictionary.cs @@ -19,7 +19,7 @@ namespace ThingsGateway.Foundation; [DebuggerDisplay("Count={Count}")] internal class InternalConcurrentDictionary : IEnumerable { - private readonly ConcurrentDictionary m_clients = new(); + private readonly NonBlockingDictionary m_clients = new(); public int Count => m_clients.Count; diff --git a/src/Foundation/ThingsGateway.Foundation/DataHandleAdapter/DeviceSingleStreamDataHandleAdapter.cs b/src/Foundation/ThingsGateway.Foundation/DataHandleAdapter/DeviceSingleStreamDataHandleAdapter.cs index 1023f7485..3e8c6d557 100644 --- a/src/Foundation/ThingsGateway.Foundation/DataHandleAdapter/DeviceSingleStreamDataHandleAdapter.cs +++ b/src/Foundation/ThingsGateway.Foundation/DataHandleAdapter/DeviceSingleStreamDataHandleAdapter.cs @@ -81,8 +81,8 @@ public class DeviceSingleStreamDataHandleAdapter : CustomDataHandlingA request = Request == null ? Request = GetInstance() : Request; else { - if (!beCached) - request = GetInstance(); + //if (!beCached) + request = GetInstance(); } var pos = byteBlock.BytesRead; diff --git a/src/Foundation/ThingsGateway.Foundation/Device/DeviceBase.cs b/src/Foundation/ThingsGateway.Foundation/Device/DeviceBase.cs index 24cea1a84..60ad76bed 100644 --- a/src/Foundation/ThingsGateway.Foundation/Device/DeviceBase.cs +++ b/src/Foundation/ThingsGateway.Foundation/Device/DeviceBase.cs @@ -583,8 +583,16 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice else { var operResult = waitData.Check(reusableTimeout.TimeoutStatus); - waitData.CompletedData.ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}"; - return waitData.CompletedData; + if(waitData.CompletedData!=null) + { + waitData.CompletedData.ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}"; + return waitData.CompletedData; + } + else + { + return new MessageBase(new OperationCanceledException()); + } + //return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" }; } } diff --git a/src/Foundation/ThingsGateway.Foundation/Localization/JsonLocalizer.cs b/src/Foundation/ThingsGateway.Foundation/Localization/JsonLocalizer.cs index 9676c945b..dc921b459 100644 --- a/src/Foundation/ThingsGateway.Foundation/Localization/JsonLocalizer.cs +++ b/src/Foundation/ThingsGateway.Foundation/Localization/JsonLocalizer.cs @@ -23,7 +23,7 @@ namespace ThingsGateway.Foundation; /// public class JsonLocalizer : IStringLocalizer { - private readonly ConcurrentDictionary _resources = new(); + private readonly NonBlockingDictionary _resources = new(); private string _folderName; private Type _type; diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Driver/Collect/CollectBase.cs b/src/Gateway/ThingsGateway.Gateway.Application/Driver/Collect/CollectBase.cs index 02237a519..0010b8391 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Driver/Collect/CollectBase.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Driver/Collect/CollectBase.cs @@ -533,7 +533,7 @@ public abstract partial class CollectBase : DriverBase { throw new NotImplementedException(); } - protected async Task Check(Dictionary writeInfoLists, ConcurrentDictionary operResults, CancellationToken cancellationToken) + protected async Task Check(Dictionary writeInfoLists, NonBlockingDictionary operResults, CancellationToken cancellationToken) { if (VariableSourceReadsEnable) { @@ -604,7 +604,7 @@ public abstract partial class CollectBase : DriverBase } } - ConcurrentDictionary> operResults = new(); + NonBlockingDictionary> operResults = new(); using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Driver/Collect/CollectFoundationBase.cs b/src/Gateway/ThingsGateway.Gateway.Application/Driver/Collect/CollectFoundationBase.cs index 40411ba01..9291cc55e 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Driver/Collect/CollectFoundationBase.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Driver/Collect/CollectFoundationBase.cs @@ -199,7 +199,7 @@ public abstract class CollectFoundationBase : CollectBase throw new NotSupportedException(); // 创建用于存储操作结果的并发字典 - ConcurrentDictionary operResults = new(); + NonBlockingDictionary operResults = new(); // 使用并发方式遍历写入信息列表,并进行异步写入操作 await writeInfoLists.ParallelForEachAsync(async (writeInfo, cancellationToken) => { diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Entity/Variable.cs b/src/Gateway/ThingsGateway.Gateway.Application/Entity/Variable.cs index 75a71da18..a5bf7fd1a 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Entity/Variable.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Entity/Variable.cs @@ -93,7 +93,7 @@ public class Variable : BaseDataEntity, IValidatableObject [System.Text.Json.Serialization.JsonIgnore] [Newtonsoft.Json.JsonIgnore] [MapperIgnore] - public ConcurrentDictionary? VariablePropertyModels; + public NonBlockingDictionary? VariablePropertyModels; /// /// 设备 diff --git a/src/Gateway/ThingsGateway.Gateway.Application/GlobalData/GlobalData.cs b/src/Gateway/ThingsGateway.Gateway.Application/GlobalData/GlobalData.cs index 91d89b8f9..965c32805 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/GlobalData/GlobalData.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/GlobalData/GlobalData.cs @@ -524,11 +524,11 @@ public static class GlobalData /// /// 内部使用的通道字典,用于存储通道对象 /// - internal static ConcurrentDictionary IdChannels { get; } = new(); + internal static NonBlockingDictionary IdChannels { get; } = new(); /// /// 内部使用的通道字典,用于存储通道对象 /// - internal static ConcurrentDictionary Channels { get; } = new(); + internal static NonBlockingDictionary Channels { get; } = new(); /// /// 只读的设备字典,提供对设备的只读访问 @@ -543,28 +543,28 @@ public static class GlobalData /// /// 内部使用的设备字典,用于存储设备对象 /// - internal static ConcurrentDictionary Devices { get; } = new(); + internal static NonBlockingDictionary Devices { get; } = new(); /// /// 内部使用的设备字典,用于存储设备对象 /// - internal static ConcurrentDictionary IdDevices { get; } = new(); + internal static NonBlockingDictionary IdDevices { get; } = new(); /// /// 内部使用的报警配置变量字典 /// - internal static ConcurrentDictionary AlarmEnableIdVariables { get; } = new(); + internal static NonBlockingDictionary AlarmEnableIdVariables { get; } = new(); /// /// 内部使用的报警配置变量字典 /// - internal static ConcurrentDictionary RealAlarmIdVariables { get; } = new(); + internal static NonBlockingDictionary RealAlarmIdVariables { get; } = new(); /// /// 内部使用的变量字典,用于存储变量对象 /// - internal static ConcurrentDictionary IdVariables { get; } = new(); + internal static NonBlockingDictionary IdVariables { get; } = new(); - internal static ConcurrentDictionary MemoryVariables { get; } = new(); + internal static NonBlockingDictionary MemoryVariables { get; } = new(); public static IReadOnlyDictionary ReadOnlyMemoryVariables => MemoryVariables; /// diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Model/ChannelRuntime.cs b/src/Gateway/ThingsGateway.Gateway.Application/Model/ChannelRuntime.cs index eed447b44..47b2eb368 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Model/ChannelRuntime.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Model/ChannelRuntime.cs @@ -84,7 +84,7 @@ public class ChannelRuntime : Channel [Newtonsoft.Json.JsonIgnore] [MapperIgnore] [AutoGenerateColumn(Ignore = true)] - internal ConcurrentDictionary? DeviceRuntimes { get; } = new(); + internal NonBlockingDictionary? DeviceRuntimes { get; } = new(); /// /// 设备数量 diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Model/DeviceRunTime.cs b/src/Gateway/ThingsGateway.Gateway.Application/Model/DeviceRunTime.cs index 7dc648832..3c0c42e74 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Model/DeviceRunTime.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Model/DeviceRunTime.cs @@ -251,7 +251,7 @@ public class DeviceRuntime : Device [Newtonsoft.Json.JsonIgnore] [MapperIgnore] [AutoGenerateColumn(Ignore = true)] - internal ConcurrentDictionary? VariableRuntimes { get; } = new(); + internal NonBlockingDictionary? VariableRuntimes { get; } = new(); /// /// 特殊方法变量 diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/DeviceService.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/DeviceService.cs index d1b3710ea..7439c7755 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/DeviceService.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/DeviceService.cs @@ -439,7 +439,7 @@ internal sealed class DeviceService : BaseService, IDeviceService // 获取所有驱动程序,并将驱动程序名称作为键构建字典 var driverPluginNameDict = _pluginService.GetPluginList().DistinctBy(a => a.Name).ToDictionary(a => a.Name); - ConcurrentDictionary, Dictionary)> propertysDict = new(); + NonBlockingDictionary, Dictionary)> propertysDict = new(); foreach (var sheetName in sheetNames) { #pragma warning disable CA1849 @@ -457,7 +457,7 @@ internal sealed class DeviceService : BaseService, IDeviceService } } - public void SetDeviceData(HashSet? dataScope, IReadOnlyDictionary deviceDicts, IReadOnlyDictionary channelDicts, Dictionary ImportPreviews, ref ImportPreviewOutput deviceImportPreview, Dictionary driverPluginNameDict, ConcurrentDictionary, Dictionary)> propertysDict, string sheetName, IEnumerable> rows) + public void SetDeviceData(HashSet? dataScope, IReadOnlyDictionary deviceDicts, IReadOnlyDictionary channelDicts, Dictionary ImportPreviews, ref ImportPreviewOutput deviceImportPreview, Dictionary driverPluginNameDict, NonBlockingDictionary, Dictionary)> propertysDict, string sheetName, IEnumerable> rows) { #region 采集设备sheet string ImportNullError = Localizer["ImportNullError"]; diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/DeviceServiceHelpers.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/DeviceServiceHelpers.cs index c86dc6038..586be2911 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/DeviceServiceHelpers.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/DeviceServiceHelpers.cs @@ -34,7 +34,7 @@ string? channelName = null) var result = new Dictionary(); result.Add(GatewayExportString.DeviceName, GetDeviceSheets(data1, deviceDicts, channelDicts, channelName)); - ConcurrentDictionary)> propertysDict = new(); + NonBlockingDictionary)> propertysDict = new(); foreach (var plugin in pluginSheetNames) { @@ -93,7 +93,7 @@ IReadOnlyDictionary? deviceDicts, static async IAsyncEnumerable> GetPluginSheets( IAsyncEnumerable data, - ConcurrentDictionary)> propertysDict, + NonBlockingDictionary)> propertysDict, string? plugin) { var enumerator = data.GetAsyncEnumerator(); @@ -129,7 +129,7 @@ IReadOnlyDictionary? deviceDicts, // 获取所有驱动程序,并将驱动程序名称作为键构建字典 var driverPluginNameDict = GlobalData.PluginService.GetPluginList().DistinctBy(a => a.Name).ToDictionary(a => a.Name); - ConcurrentDictionary, Dictionary)> propertysDict = new(); + NonBlockingDictionary, Dictionary)> propertysDict = new(); var sheetNames = uSheetDatas.sheets.Keys.ToList(); foreach (var sheetName in sheetNames) diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/DeviceServiceHelpers.m.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/DeviceServiceHelpers.m.cs index e1eab5071..1d5591cd3 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/DeviceServiceHelpers.m.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/DeviceServiceHelpers.m.cs @@ -45,7 +45,7 @@ HashSet pluginSheetNames, var result = new Dictionary(); result.Add(GatewayExportString.DeviceName, GetDeviceSheets(data, deviceDicts, channelDicts, channelName)); - ConcurrentDictionary)> propertysDict = new(); + NonBlockingDictionary)> propertysDict = new(); foreach (var plugin in pluginSheetNames) { @@ -130,7 +130,7 @@ string? channelName) static IEnumerable> GetPluginSheets( IEnumerable data, - ConcurrentDictionary)> propertysDict, + NonBlockingDictionary)> propertysDict, string? plugin) { foreach (var device in data) @@ -145,7 +145,7 @@ string? channelName) - static Dictionary GetPluginRows(Device device, string? plugin, ConcurrentDictionary)> propertysDict) + static Dictionary GetPluginRows(Device device, string? plugin, NonBlockingDictionary)> propertysDict) { Dictionary driverInfo = new(); var propDict = device.DevicePropertys; diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/IDeviceService.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/IDeviceService.cs index 6252d6868..9071c9ae7 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/IDeviceService.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/Device/IDeviceService.cs @@ -110,7 +110,7 @@ internal interface IDeviceService /// 保存是否成功的异步任务 Task BatchSaveDeviceAsync(List input, ItemChangedType type); - void SetDeviceData(HashSet? dataScope, IReadOnlyDictionary deviceDicts, IReadOnlyDictionary channelDicts, Dictionary ImportPreviews, ref ImportPreviewOutput deviceImportPreview, Dictionary driverPluginNameDict, ConcurrentDictionary, Dictionary)> propertysDict, string sheetName, IEnumerable> rows); + void SetDeviceData(HashSet? dataScope, IReadOnlyDictionary deviceDicts, IReadOnlyDictionary channelDicts, Dictionary ImportPreviews, ref ImportPreviewOutput deviceImportPreview, Dictionary driverPluginNameDict, NonBlockingDictionary, Dictionary)> propertysDict, string sheetName, IEnumerable> rows); /// /// 保存是否输出日志和日志等级 diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/GatewayMonitor/ChannelManage/ChannelThreadManage.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/GatewayMonitor/ChannelManage/ChannelThreadManage.cs index 0ddde6e24..b3343bd0f 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/GatewayMonitor/ChannelManage/ChannelThreadManage.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/GatewayMonitor/ChannelManage/ChannelThreadManage.cs @@ -26,7 +26,7 @@ internal sealed class ChannelThreadManage : IChannelThreadManage _logger = App.RootServices.GetService().CreateLogger($"ChannelThreadService"); } - public ConcurrentDictionary DeviceThreadManages { get; } = new(); + public NonBlockingDictionary DeviceThreadManages { get; } = new(); #region 设备管理 diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/GatewayMonitor/ChannelManage/IChannelThreadManage.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/GatewayMonitor/ChannelManage/IChannelThreadManage.cs index 6047ea803..c89e9d1ec 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/GatewayMonitor/ChannelManage/IChannelThreadManage.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/GatewayMonitor/ChannelManage/IChannelThreadManage.cs @@ -14,7 +14,7 @@ namespace ThingsGateway.Gateway.Application; public interface IChannelThreadManage : IAsyncDisposable { - ConcurrentDictionary DeviceThreadManages { get; } + NonBlockingDictionary DeviceThreadManages { get; } Task RestartChannelAsync(ChannelRuntime channelRuntime); Task RestartChannelAsync(IList channelRuntimes); diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/GatewayMonitor/DeviceManage/DeviceThreadManage.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/GatewayMonitor/DeviceManage/DeviceThreadManage.cs index fff30a7cc..8d45953ab 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/GatewayMonitor/DeviceManage/DeviceThreadManage.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/GatewayMonitor/DeviceManage/DeviceThreadManage.cs @@ -152,18 +152,18 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage /// /// 任务 /// - internal ConcurrentDictionary DriverTasks { get; } = new(); + internal NonBlockingDictionary DriverTasks { get; } = new(); public int TaskCount => DriverTasks.Count; /// /// 取消令箭列表 /// - private ConcurrentDictionary CancellationTokenSources { get; set; } = new(); + private NonBlockingDictionary CancellationTokenSources { get; set; } = new(); /// /// 插件列表 /// - private ConcurrentDictionary Drivers { get; set; } = new(); + private NonBlockingDictionary Drivers { get; set; } = new(); public IChannelThreadManage ChannelThreadManage { get; internal set; } diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/Plugin/PluginService.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/Plugin/PluginService.cs index a41bd5260..ab86c4224 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/Plugin/PluginService.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/Plugin/PluginService.cs @@ -62,7 +62,7 @@ internal sealed class PluginService : IPluginService /// /// 插件文件名称/插件域 /// - private ConcurrentDictionary _assemblyLoadContextDict { get; } = new(); + private NonBlockingDictionary _assemblyLoadContextDict { get; } = new(); /// /// 主程序上下文中的插件FullName/插件Type @@ -73,7 +73,7 @@ internal sealed class PluginService : IPluginService /// /// 插件FullName/插件Type /// - private ConcurrentDictionary _driverBaseDict { get; } = new(); + private NonBlockingDictionary _driverBaseDict { get; } = new(); #region public @@ -615,7 +615,7 @@ internal sealed class PluginService : IPluginService // 获取私有字段 FieldInfo fieldInfo = typeof(ResourceManagerStringLocalizerFactory).GetField("_localizerCache", BindingFlags.Instance | BindingFlags.NonPublic); // 获取字段的值 - var dictionary = (ConcurrentDictionary)fieldInfo.GetValue(App.StringLocalizerFactory); + var dictionary = (NonBlockingDictionary)fieldInfo.GetValue(App.StringLocalizerFactory); foreach (var item in _assemblyLoadContextDict) { var ids = item.Value.Assembly.ExportedTypes.Select(b => b.AssemblyQualifiedName).ToHashSet(); diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/Plugin/PluginServiceUtil.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/Plugin/PluginServiceUtil.cs index 48f516c95..e00e06e22 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/Plugin/PluginServiceUtil.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/Plugin/PluginServiceUtil.cs @@ -102,7 +102,7 @@ public static class PluginServiceUtil /// /// 通过实体赋值到字典中 /// - public static Dictionary> SetDict(ConcurrentDictionary? models) + public static Dictionary> SetDict(NonBlockingDictionary? models) { Dictionary> results = new(); models ??= new(); diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/Rpc/RpcService.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/Rpc/RpcService.cs index ea1b7836e..68f13eca2 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/Rpc/RpcService.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/Rpc/RpcService.cs @@ -54,7 +54,7 @@ internal sealed class RpcService : IRpcService Dictionary memoryVariables = new(); // 用于存储结果的并发字典 - ConcurrentDictionary> results = new(); + NonBlockingDictionary> results = new(); deviceDatas.ForEach(a => results.TryAdd(a.Key, new())); // 对每个要操作的变量进行检查和处理(内存变量) diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/RulesEngine/Node/NodeData/Trigger/AlarmChangedTriggerNode.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/RulesEngine/Node/NodeData/Trigger/AlarmChangedTriggerNode.cs index 60924ab44..d86c6015c 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/RulesEngine/Node/NodeData/Trigger/AlarmChangedTriggerNode.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/RulesEngine/Node/NodeData/Trigger/AlarmChangedTriggerNode.cs @@ -41,9 +41,9 @@ public class AlarmChangedTriggerNode : VariableNode, ITriggerNode, IDisposable return Task.CompletedTask; } - public static ConcurrentDictionary>> AlarmChangedTriggerNodeDict = new(); + public static NonBlockingDictionary>> AlarmChangedTriggerNodeDict = new(); - public static ConcurrentDictionary> FuncDict = new(); + public static NonBlockingDictionary> FuncDict = new(); public static BlockingCollection AlarmVariables = new(); static AlarmChangedTriggerNode() diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/RulesEngine/Node/NodeData/Trigger/ValueChangedTriggerNode.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/RulesEngine/Node/NodeData/Trigger/ValueChangedTriggerNode.cs index 8f37f84c9..c06d36de4 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/RulesEngine/Node/NodeData/Trigger/ValueChangedTriggerNode.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/RulesEngine/Node/NodeData/Trigger/ValueChangedTriggerNode.cs @@ -40,8 +40,8 @@ public class ValueChangedTriggerNode : VariableNode, ITriggerNode, IDisposable } return Task.CompletedTask; } - public static ConcurrentDictionary>> ValueChangedTriggerNodeDict = new(); - public static ConcurrentDictionary> FuncDict = new(); + public static NonBlockingDictionary>> ValueChangedTriggerNodeDict = new(); + public static NonBlockingDictionary> FuncDict = new(); public static BlockingCollection VariableBasicDatas = new(); static ValueChangedTriggerNode() diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/Variable/IVariableService.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/Variable/IVariableService.cs index e589de130..0c41fdbd2 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/Variable/IVariableService.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/Variable/IVariableService.cs @@ -106,7 +106,7 @@ internal interface IVariableService Task UpdateInitValueAsync(List variables); Task> GetByDeviceIdAsync(List deviceIds); - ImportPreviewOutput> SetVariableData(HashSet? dataScope, IReadOnlyDictionary deviceDicts, Dictionary ImportPreviews, ImportPreviewOutput> deviceImportPreview, Dictionary driverPluginNameDict, ConcurrentDictionary, Dictionary)> propertysDict, string sheetName, IEnumerable> rows); + ImportPreviewOutput> SetVariableData(HashSet? dataScope, IReadOnlyDictionary deviceDicts, Dictionary ImportPreviews, ImportPreviewOutput> deviceImportPreview, Dictionary driverPluginNameDict, NonBlockingDictionary, Dictionary)> propertysDict, string sheetName, IEnumerable> rows); List GetAllVariableRuntime(); Task> PreviewAsync(string filePath); Task> ImportVariableAsync(List upData, List insertData); diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/Variable/VariableService.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/Variable/VariableService.cs index c941e9232..0123757c6 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/Variable/VariableService.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/Variable/VariableService.cs @@ -743,7 +743,7 @@ internal sealed class VariableService : BaseService, IVariableService ImportPreviewOutput> deviceImportPreview = new(); var driverPluginNameDict = _pluginService.GetPluginList().ToDictionary(a => a.Name); - ConcurrentDictionary, Dictionary)> propertysDict = new(); + NonBlockingDictionary, Dictionary)> propertysDict = new(); // 遍历每个工作表 foreach (var sheetName in sheetNames) @@ -765,7 +765,7 @@ internal sealed class VariableService : BaseService, IVariableService } } - public ImportPreviewOutput> SetVariableData(HashSet? dataScope, IReadOnlyDictionary deviceDicts, Dictionary ImportPreviews, ImportPreviewOutput> deviceImportPreview, Dictionary driverPluginNameDict, ConcurrentDictionary, Dictionary)> propertysDict, string sheetName, IEnumerable> rows) + public ImportPreviewOutput> SetVariableData(HashSet? dataScope, IReadOnlyDictionary deviceDicts, Dictionary ImportPreviews, ImportPreviewOutput> deviceImportPreview, Dictionary driverPluginNameDict, NonBlockingDictionary, Dictionary)> propertysDict, string sheetName, IEnumerable> rows) { string ImportNullError = Localizer["ImportNullError"]; string RedundantDeviceError = Localizer["RedundantDeviceError"]; diff --git a/src/Gateway/ThingsGateway.Gateway.Application/Services/Variable/VariableServiceHelpers.cs b/src/Gateway/ThingsGateway.Gateway.Application/Services/Variable/VariableServiceHelpers.cs index ad14d4a3e..c77671c99 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/Services/Variable/VariableServiceHelpers.cs +++ b/src/Gateway/ThingsGateway.Gateway.Application/Services/Variable/VariableServiceHelpers.cs @@ -94,7 +94,7 @@ IReadOnlyDictionary channelDicts) string? deviceName = null) { var sheets = new Dictionary(); - var propertysDict = new ConcurrentDictionary)>(); + var propertysDict = new NonBlockingDictionary)>(); // 主变量页 sheets.Add(GatewayExportString.VariableName, GetVariableSheets(data, deviceDicts, deviceName)); @@ -198,7 +198,7 @@ string? deviceName) IReadOnlyDictionary channelDicts, string plugin, Dictionary pluginDrivers, - ConcurrentDictionary)> propertysDict) + NonBlockingDictionary)> propertysDict) { if (!pluginDrivers.TryGetValue(plugin, out var variablePropertyBase)) yield break; @@ -271,7 +271,7 @@ string? deviceName) string? deviceName = null) { var sheets = new Dictionary(); - var propertysDict = new ConcurrentDictionary)>(); + var propertysDict = new NonBlockingDictionary)>(); // 主变量页 sheets.Add(GatewayExportString.VariableName, GetVariableSheets(data, deviceDicts, deviceName)); @@ -315,7 +315,7 @@ string? deviceName) IReadOnlyDictionary channelDicts, string plugin, Dictionary pluginDrivers, - ConcurrentDictionary)> propertysDict) + NonBlockingDictionary)> propertysDict) { if (!pluginDrivers.TryGetValue(plugin, out var variablePropertyBase)) yield break; @@ -370,8 +370,8 @@ string? deviceName) ConcurrentList> variableExports = new(); ConcurrentList> alarmExports = new(); //变量附加属性,转成Dict<表名,List>>的形式 - ConcurrentDictionary>> devicePropertys = new(); - ConcurrentDictionary)> propertysDict = new(); + NonBlockingDictionary>> devicePropertys = new(); + NonBlockingDictionary)> propertysDict = new(); #region 列名称 @@ -596,7 +596,7 @@ string? deviceName) // 获取驱动插件的全名和名称的字典 var driverPluginFullNameDict = plugins.ToDictionary(a => a.FullName); var driverPluginNameDict = plugins.ToDictionary(a => a.Name); - ConcurrentDictionary, Dictionary)> propertysDict = new(); + NonBlockingDictionary, Dictionary)> propertysDict = new(); var sheetNames = uSheetDatas.sheets.Keys.ToList(); foreach (var sheetName in sheetNames) diff --git a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableEditComponent.razor.cs b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableEditComponent.razor.cs index 4ee70887c..d2980fa33 100644 --- a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableEditComponent.razor.cs +++ b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableEditComponent.razor.cs @@ -129,8 +129,8 @@ public partial class VariableEditComponent [CascadingParameter] private Func? OnCloseAsync { get; set; } - private ConcurrentDictionary>? VariablePropertyEditors { get; set; } = new(); - private ConcurrentDictionary? VariablePropertyRenderFragments { get; set; } = new(); + private NonBlockingDictionary>? VariablePropertyEditors { get; set; } = new(); + private NonBlockingDictionary? VariablePropertyRenderFragments { get; set; } = new(); public async Task ValidSubmit(EditContext editContext) { diff --git a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableRow.razor.cs b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableRow.razor.cs index 8c0b08be8..ea99cf72e 100644 --- a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableRow.razor.cs +++ b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableRow.razor.cs @@ -159,7 +159,7 @@ public partial class VariableRow : IDisposable var index = Columns.IndexOf(col); return middle < index; } - private ConcurrentDictionary CellClassStringCache { get; } = new(ReferenceEqualityComparer.Instance); + private NonBlockingDictionary CellClassStringCache { get; } = new(ReferenceEqualityComparer.Instance); /// /// 获得 Cell 文字样式 @@ -195,7 +195,7 @@ public partial class VariableRow : IDisposable private bool AllowResizing = true; private bool IsTree = false; - private ConcurrentDictionary FixedCellClassStringCache { get; } = new(ReferenceEqualityComparer.Instance); + private NonBlockingDictionary FixedCellClassStringCache { get; } = new(ReferenceEqualityComparer.Instance); /// /// 获得指定列头固定列样式 /// @@ -225,7 +225,7 @@ public partial class VariableRow : IDisposable public Func> ColumnsFunc { get; set; } public List Columns => ColumnsFunc(); - private ConcurrentDictionary LastFixedColumnCache { get; } = new(ReferenceEqualityComparer.Instance); + private NonBlockingDictionary LastFixedColumnCache { get; } = new(ReferenceEqualityComparer.Instance); private bool IsLastColumn(ITableColumn col) { if (LastFixedColumnCache.TryGetValue(col, out var cached)) @@ -247,7 +247,7 @@ public partial class VariableRow : IDisposable } } - private ConcurrentDictionary FirstFixedColumnCache { get; } = new(ReferenceEqualityComparer.Instance); + private NonBlockingDictionary FirstFixedColumnCache { get; } = new(ReferenceEqualityComparer.Instance); private bool IsFirstColumn(ITableColumn col) { if (FirstFixedColumnCache.TryGetValue(col, out var cached)) diff --git a/src/Plugin/ThingsGateway.Foundation.Modbus/Master/Core/ModbusTcpMessage.cs b/src/Plugin/ThingsGateway.Foundation.Modbus/Master/Core/ModbusTcpMessage.cs index 8dbe08c8f..ef3e9db72 100644 --- a/src/Plugin/ThingsGateway.Foundation.Modbus/Master/Core/ModbusTcpMessage.cs +++ b/src/Plugin/ThingsGateway.Foundation.Modbus/Master/Core/ModbusTcpMessage.cs @@ -42,51 +42,60 @@ public class ModbusTcpMessage : MessageBase, IResultMessage public override FilterResult CheckBody(ref TByteBlock byteBlock) { - var f = Response.FunctionCode > 0x30 ? Response.FunctionCode - 0x30 : Response.FunctionCode; - if (error) + try { - Response.ErrorCode = ReaderExtension.ReadValue(ref byteBlock); - OperCode = Response.ErrorCode; - ErrorMessage = ModbusHelper.GetDescriptionByErrorCode(Response.ErrorCode.Value); - ErrorType = ErrorTypeEnum.DeviceError; - } - else - { - Response.ErrorCode = null; - } - if (Response.ErrorCode != null) - { - return FilterResult.Success; - } - if (f <= 4) - { - OperCode = 0; - Response.Length = ReaderExtension.ReadValue(ref byteBlock); - Content = byteBlock.ToArrayTake(BodyLength - 1); - Response.MasterWriteDatas = Content; - return FilterResult.Success; + var f = Response.FunctionCode > 0x30 ? Response.FunctionCode - 0x30 : Response.FunctionCode; + if (error) + { + Response.ErrorCode = ReaderExtension.ReadValue(ref byteBlock); + OperCode = Response.ErrorCode; + ErrorMessage = ModbusHelper.GetDescriptionByErrorCode(Response.ErrorCode.Value); + ErrorType = ErrorTypeEnum.DeviceError; + } + else + { + Response.ErrorCode = null; + } + if (Response.ErrorCode != null) + { + return FilterResult.Success; + } + + if (f <= 4) + { + OperCode = 0; + Response.Length = ReaderExtension.ReadValue(ref byteBlock); + Content = byteBlock.ToArrayTake(BodyLength - 1); + Response.MasterWriteDatas = Content; + return FilterResult.Success; + } + else if (f == 5 || f == 6) + { + Response.StartAddress = ReaderExtension.ReadValue(ref byteBlock, EndianType.Big); + OperCode = 0; + Content = byteBlock.ToArrayTake(BodyLength - 2); + Response.MasterWriteDatas = Content; + return FilterResult.Success; + } + else if (f == 15 || f == 16) + { + Response.StartAddress = ReaderExtension.ReadValue(ref byteBlock, EndianType.Big); + OperCode = 0; + Response.Length = ReaderExtension.ReadValue(ref byteBlock, EndianType.Big); + Content = Array.Empty(); + return FilterResult.Success; + } + else + { + OperCode = 999; + ErrorMessage = AppResource.ModbusError1; + } + } - else if (f == 5 || f == 6) + catch (Exception ex) { - Response.StartAddress = ReaderExtension.ReadValue(ref byteBlock, EndianType.Big); - OperCode = 0; - Content = byteBlock.ToArrayTake(BodyLength - 2); - Response.MasterWriteDatas = Content; - return FilterResult.Success; - } - else if (f == 15 || f == 16) - { - Response.StartAddress = ReaderExtension.ReadValue(ref byteBlock, EndianType.Big); - OperCode = 0; - Response.Length = ReaderExtension.ReadValue(ref byteBlock, EndianType.Big); - Content = Array.Empty(); - return FilterResult.Success; - } - else - { - OperCode = 999; - ErrorMessage = AppResource.ModbusError1; + throw new Exception($"Data length:{byteBlock.TotalSequence.Length} bodyLength:{BodyLength} BytesRead:{byteBlock.BytesRead} BytesRemaining:{byteBlock.BytesRemaining}", ex); } return FilterResult.GoOn; } diff --git a/src/Plugin/ThingsGateway.Foundation.Modbus/Master/ModbusMaster.cs b/src/Plugin/ThingsGateway.Foundation.Modbus/Master/ModbusMaster.cs index 90aa77f21..3f338a9ed 100644 --- a/src/Plugin/ThingsGateway.Foundation.Modbus/Master/ModbusMaster.cs +++ b/src/Plugin/ThingsGateway.Foundation.Modbus/Master/ModbusMaster.cs @@ -60,14 +60,19 @@ public partial class ModbusMaster : DtuServiceDeviceBase, IModbusAddress return new DeviceSingleStreamDataHandleAdapter() { CacheTimeout = TimeSpan.FromMilliseconds(Channel.ChannelOptions.CacheTimeout), + IsSingleThread = false }; case ChannelTypeEnum.UdpSession: - return new DeviceUdpDataHandleAdapter(); + return new DeviceUdpDataHandleAdapter() + { + IsSingleThread = false + }; } return new DeviceSingleStreamDataHandleAdapter() { CacheTimeout = TimeSpan.FromMilliseconds(Channel.ChannelOptions.CacheTimeout), + IsSingleThread = false }; case ModbusTypeEnum.ModbusRtu: diff --git a/src/Plugin/ThingsGateway.Foundation.Modbus/Slave/ModbusSlave.cs b/src/Plugin/ThingsGateway.Foundation.Modbus/Slave/ModbusSlave.cs index 6472503a7..0e221aeb5 100644 --- a/src/Plugin/ThingsGateway.Foundation.Modbus/Slave/ModbusSlave.cs +++ b/src/Plugin/ThingsGateway.Foundation.Modbus/Slave/ModbusSlave.cs @@ -27,22 +27,22 @@ public class ModbusSlave : DeviceBase, IModbusAddress /// /// 继电器 /// - private ConcurrentDictionary ModbusServer01ByteBlocks = new(); + private NonBlockingDictionary ModbusServer01ByteBlocks = new(); /// /// 开关输入 /// - private ConcurrentDictionary ModbusServer02ByteBlocks = new(); + private NonBlockingDictionary ModbusServer02ByteBlocks = new(); /// /// 输入寄存器 /// - private ConcurrentDictionary ModbusServer03ByteBlocks = new(); + private NonBlockingDictionary ModbusServer03ByteBlocks = new(); /// /// 保持寄存器 /// - private ConcurrentDictionary ModbusServer04ByteBlocks = new(); + private NonBlockingDictionary ModbusServer04ByteBlocks = new(); /// public override void InitChannel(IChannel channel, ILog? deviceLog = null) diff --git a/src/Plugin/ThingsGateway.Foundation.OpcDa/ThingsGateway.Foundation.OpcDa.csproj b/src/Plugin/ThingsGateway.Foundation.OpcDa/ThingsGateway.Foundation.OpcDa.csproj index 092eb8ed2..b20d75f43 100644 --- a/src/Plugin/ThingsGateway.Foundation.OpcDa/ThingsGateway.Foundation.OpcDa.csproj +++ b/src/Plugin/ThingsGateway.Foundation.OpcDa/ThingsGateway.Foundation.OpcDa.csproj @@ -17,5 +17,6 @@ Never + diff --git a/src/Plugin/ThingsGateway.Foundation.OpcUa/ThingsGateway.Foundation.OpcUa.csproj b/src/Plugin/ThingsGateway.Foundation.OpcUa/ThingsGateway.Foundation.OpcUa.csproj index 7dda1d76c..faafb71a7 100644 --- a/src/Plugin/ThingsGateway.Foundation.OpcUa/ThingsGateway.Foundation.OpcUa.csproj +++ b/src/Plugin/ThingsGateway.Foundation.OpcUa/ThingsGateway.Foundation.OpcUa.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Plugin/ThingsGateway.Foundation.SiemensS7/S7/SiemensS7Master.cs b/src/Plugin/ThingsGateway.Foundation.SiemensS7/S7/SiemensS7Master.cs index 7874cdfb6..dd72ff180 100644 --- a/src/Plugin/ThingsGateway.Foundation.SiemensS7/S7/SiemensS7Master.cs +++ b/src/Plugin/ThingsGateway.Foundation.SiemensS7/S7/SiemensS7Master.cs @@ -117,16 +117,21 @@ public partial class SiemensS7Master : DeviceBase case ChannelTypeEnum.SerialPort: return new DeviceSingleStreamDataHandleAdapter { - CacheTimeout = TimeSpan.FromMilliseconds(Channel.ChannelOptions.CacheTimeout) + CacheTimeout = TimeSpan.FromMilliseconds(Channel.ChannelOptions.CacheTimeout), + IsSingleThread = false }; case ChannelTypeEnum.UdpSession: - return new DeviceUdpDataHandleAdapter(); + return new DeviceUdpDataHandleAdapter() + { + IsSingleThread = false + }; } return new DeviceSingleStreamDataHandleAdapter { - CacheTimeout = TimeSpan.FromMilliseconds(Channel.ChannelOptions.CacheTimeout) + CacheTimeout = TimeSpan.FromMilliseconds(Channel.ChannelOptions.CacheTimeout), + IsSingleThread = false }; } diff --git a/src/Plugin/ThingsGateway.Plugin.DB/SqlDB/SqlDbProducer.other.cs b/src/Plugin/ThingsGateway.Plugin.DB/SqlDB/SqlDbProducer.other.cs index 9a046db10..03116cfcd 100644 --- a/src/Plugin/ThingsGateway.Plugin.DB/SqlDB/SqlDbProducer.other.cs +++ b/src/Plugin/ThingsGateway.Plugin.DB/SqlDB/SqlDbProducer.other.cs @@ -26,7 +26,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable { #if !Management private volatile bool _initRealData; - private ConcurrentDictionary RealTimeVariables { get; } = new ConcurrentDictionary(); + private NonBlockingDictionary RealTimeVariables { get; } = new NonBlockingDictionary(); protected override ValueTask UpdateVarModel(List> item, CancellationToken cancellationToken) { diff --git a/src/Plugin/ThingsGateway.Plugin.Modbus/ModbusSlave/ModbusSlave.cs b/src/Plugin/ThingsGateway.Plugin.Modbus/ModbusSlave/ModbusSlave.cs index e98cbbfc3..81c141c20 100644 --- a/src/Plugin/ThingsGateway.Plugin.Modbus/ModbusSlave/ModbusSlave.cs +++ b/src/Plugin/ThingsGateway.Plugin.Modbus/ModbusSlave/ModbusSlave.cs @@ -28,7 +28,7 @@ public class ModbusSlave : BusinessBase { private readonly ModbusSlaveProperty _driverPropertys = new(); - private readonly ConcurrentDictionary ModbusVariableQueue = new(); + private readonly NonBlockingDictionary ModbusVariableQueue = new(); private readonly ModbusSlaveVariableProperty _variablePropertys = new(); diff --git a/src/Plugin/ThingsGateway.Plugin.OpcDa/OpcDaMaster/OpcDaMaster.cs b/src/Plugin/ThingsGateway.Plugin.OpcDa/OpcDaMaster/OpcDaMaster.cs index 790b7ff8e..fb4a5601b 100644 --- a/src/Plugin/ThingsGateway.Plugin.OpcDa/OpcDaMaster/OpcDaMaster.cs +++ b/src/Plugin/ThingsGateway.Plugin.OpcDa/OpcDaMaster/OpcDaMaster.cs @@ -162,7 +162,7 @@ public class OpcDaMaster : CollectBase using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); await ValueTask.CompletedTask.ConfigureAwait(false); var result = _plc.WriteItem(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value.GetObjectFromJToken()!)); - var results = new ConcurrentDictionary(result.ToDictionary>, string, OperResult>(a => writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key).Name, a => + var results = new NonBlockingDictionary(result.ToDictionary>, string, OperResult>(a => writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key).Name, a => { if (!a.Value.Item1) return new OperResult(a.Value.Item2); diff --git a/src/Plugin/ThingsGateway.Plugin.OpcUa/OpcUaMaster/OpcUaMaster.cs b/src/Plugin/ThingsGateway.Plugin.OpcUa/OpcUaMaster/OpcUaMaster.cs index 1dab1361d..71a388ee3 100644 --- a/src/Plugin/ThingsGateway.Plugin.OpcUa/OpcUaMaster/OpcUaMaster.cs +++ b/src/Plugin/ThingsGateway.Plugin.OpcUa/OpcUaMaster/OpcUaMaster.cs @@ -283,7 +283,7 @@ public class OpcUaMaster : CollectBase { using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); var result = await _plc.WriteNodeAsync(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value), cancellationToken).ConfigureAwait(false); - var results = new ConcurrentDictionary(result.ToDictionary>, string, OperResult>(a => writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key)?.Name! + var results = new NonBlockingDictionary(result.ToDictionary>, string, OperResult>(a => writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key)?.Name! , a => { if (!a.Value.Item1) diff --git a/src/Plugin/ThingsGateway.Plugin.OpcUa/OpcUaServer/OpcUaServer.cs b/src/Plugin/ThingsGateway.Plugin.OpcUa/OpcUaServer/OpcUaServer.cs index 398f5ef99..9186be929 100644 --- a/src/Plugin/ThingsGateway.Plugin.OpcUa/OpcUaServer/OpcUaServer.cs +++ b/src/Plugin/ThingsGateway.Plugin.OpcUa/OpcUaServer/OpcUaServer.cs @@ -50,7 +50,7 @@ public partial class OpcUaServer : BusinessBase #if !Management private ThingsGatewayServer m_server; protected IStringLocalizer Localizer { get; private set; } - private ConcurrentDictionary CollectVariableRuntimes { get; set; } = new(); + private NonBlockingDictionary CollectVariableRuntimes { get; set; } = new(); private static readonly string[] separator = new string[] { ";" }; diff --git a/src/Plugin/ThingsGateway.Plugin.SiemensS7/SiemensS7Master/SiemensS7Master.cs b/src/Plugin/ThingsGateway.Plugin.SiemensS7/SiemensS7Master/SiemensS7Master.cs index 419068205..92c4ed9ef 100644 --- a/src/Plugin/ThingsGateway.Plugin.SiemensS7/SiemensS7Master/SiemensS7Master.cs +++ b/src/Plugin/ThingsGateway.Plugin.SiemensS7/SiemensS7Master/SiemensS7Master.cs @@ -128,7 +128,7 @@ public class SiemensS7Master : CollectFoundationBase throw new NotSupportedException(); // 创建用于存储操作结果的并发字典 - ConcurrentDictionary operResults = new(); + NonBlockingDictionary operResults = new(); //转换 Dictionary addresses = new(); diff --git a/src/ThingsGateway.ScriptDebug/Test/TestKafkaDynamicModel.cs b/src/ThingsGateway.ScriptDebug/Test/TestKafkaDynamicModel.cs index d81081985..523ac24d1 100644 --- a/src/ThingsGateway.ScriptDebug/Test/TestKafkaDynamicModel.cs +++ b/src/ThingsGateway.ScriptDebug/Test/TestKafkaDynamicModel.cs @@ -15,7 +15,7 @@ public class TestKafkaDynamicModel1 : DynamicModelBase } - private ConcurrentDictionary, DateTime> EventKeyTimes = new(); + private NonBlockingDictionary, DateTime> EventKeyTimes = new(); public override IEnumerable GetList(IEnumerable datas) {