From 7cf3cca8eb3ab3f1c3c49f24b38cbb5e975940f9 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Wed, 21 Aug 2024 12:23:35 +0200 Subject: [PATCH 1/3] Improved performance for .NET 9.0+ through System.Threading.Lock. --- Directory.Build.props | 2 +- src/Cuemon.Core/Cuemon.Core.csproj | 4 ++++ src/Cuemon.Core/Runtime/Dependency.cs | 3 ++- src/Cuemon.Core/Runtime/FileWatcher.cs | 3 ++- src/Cuemon.Core/Runtime/Watcher.cs | 2 +- src/Cuemon.Data/BulkCopyDataReader.cs | 3 ++- src/Cuemon.Data/DatabaseWatcher.cs | 3 ++- .../Bootstrapper.cs | 3 ++- src/Cuemon.Extensions.AspNetCore.Text.Json/Bootstrapper.cs | 3 ++- src/Cuemon.Extensions.AspNetCore.Xml/Bootstrapper.cs | 3 ++- src/Cuemon.Extensions.Net/Http/SlimHttpClientFactory.cs | 2 +- .../Formatters/NewtonsoftJsonFormatterOptions.cs | 3 ++- .../CacheEnumerableExtensions.cs | 3 ++- .../Formatters/JsonFormatterOptions.cs | 3 ++- .../Formatters/YamlFormatterOptions.cs | 3 ++- .../Serialization/Formatters/XmlFormatterOptions.cs | 3 ++- 16 files changed, 31 insertions(+), 15 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index a816f0d83..38f029809 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,7 @@ $([MSBuild]::IsOSPlatform('Windows')) true false - latest + preview diff --git a/src/Cuemon.Core/Cuemon.Core.csproj b/src/Cuemon.Core/Cuemon.Core.csproj index 0a1710f99..65956938f 100644 --- a/src/Cuemon.Core/Cuemon.Core.csproj +++ b/src/Cuemon.Core/Cuemon.Core.csproj @@ -13,4 +13,8 @@ action-factory bit-unit byte-unit binary-prefix decimal-prefix prefix-multiple multiple-table calculator configure configure-revert configure-exchange configurable condition options-pattern data-reader decorator delimited-string disposable finalize-disposable safe-invoke safe-invoke-async func-factory patterns reference-project clean-architecture clean-code task-action-factory task-func-factory template template-factory time-range time-unit validator guard text-encoding parser-factory security aes-cryptor cyclic-redundancy-check fowler-noll-vo-hash hash-factory hash-result hmac-message-digest hmac-secure-hash-algorithm keyed-crypto-hash keyed-crypto-algorithm message-digest non-crypto-algorithm secure-hash-algorithm unkeyed-crypto-hash + + + + diff --git a/src/Cuemon.Core/Runtime/Dependency.cs b/src/Cuemon.Core/Runtime/Dependency.cs index 5b4bdb63d..f37c74f6b 100644 --- a/src/Cuemon.Core/Runtime/Dependency.cs +++ b/src/Cuemon.Core/Runtime/Dependency.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Cuemon.Runtime @@ -12,7 +13,7 @@ public abstract class Dependency : IDependency { private IEnumerable _watchers; private readonly Func, IEnumerable> _watchersHandler; - private readonly object _locker = new(); + private readonly Lock _locker = new(); /// /// Initializes a new instance of the class. diff --git a/src/Cuemon.Core/Runtime/FileWatcher.cs b/src/Cuemon.Core/Runtime/FileWatcher.cs index b40c2e7ab..851420ba9 100644 --- a/src/Cuemon.Core/Runtime/FileWatcher.cs +++ b/src/Cuemon.Core/Runtime/FileWatcher.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using Cuemon.Security; @@ -11,7 +12,7 @@ namespace Cuemon.Runtime /// public class FileWatcher : Watcher { - private readonly object _locker = new(); + private readonly Lock _locker = new(); /// /// Initializes a new instance of the class. diff --git a/src/Cuemon.Core/Runtime/Watcher.cs b/src/Cuemon.Core/Runtime/Watcher.cs index 6f3f5f6d5..a0bbee15f 100644 --- a/src/Cuemon.Core/Runtime/Watcher.cs +++ b/src/Cuemon.Core/Runtime/Watcher.cs @@ -10,7 +10,7 @@ namespace Cuemon.Runtime /// public abstract class Watcher : Disposable, IWatcher { - private readonly object _locker = new(); + private readonly Lock _locker = new(); private Timer _watcherTimer; private Timer _watcherPostponingTimer; diff --git a/src/Cuemon.Data/BulkCopyDataReader.cs b/src/Cuemon.Data/BulkCopyDataReader.cs index face81023..77ef02076 100644 --- a/src/Cuemon.Data/BulkCopyDataReader.cs +++ b/src/Cuemon.Data/BulkCopyDataReader.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Text; +using System.Threading; namespace Cuemon.Data { @@ -14,7 +15,7 @@ namespace Cuemon.Data /// public sealed class BulkCopyDataReader : DbDataReader { - private static readonly object PadLock = new(); + private static readonly Lock PadLock = new(); private IOrderedDictionary _defaultFields; /// diff --git a/src/Cuemon.Data/DatabaseWatcher.cs b/src/Cuemon.Data/DatabaseWatcher.cs index c9eaef601..94685e2af 100644 --- a/src/Cuemon.Data/DatabaseWatcher.cs +++ b/src/Cuemon.Data/DatabaseWatcher.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Data; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Cuemon.Runtime; using Cuemon.Security; @@ -14,7 +15,7 @@ namespace Cuemon.Data /// public class DatabaseWatcher : Watcher { - private readonly object _locker = new(); + private readonly Lock _locker = new(); /// /// Initializes a new instance of the class. diff --git a/src/Cuemon.Extensions.AspNetCore.Newtonsoft.Json/Bootstrapper.cs b/src/Cuemon.Extensions.AspNetCore.Newtonsoft.Json/Bootstrapper.cs index e8e19a0ba..1a477cbfb 100644 --- a/src/Cuemon.Extensions.AspNetCore.Newtonsoft.Json/Bootstrapper.cs +++ b/src/Cuemon.Extensions.AspNetCore.Newtonsoft.Json/Bootstrapper.cs @@ -1,11 +1,12 @@ using Cuemon.Extensions.AspNetCore.Newtonsoft.Json.Converters; using Cuemon.Extensions.Newtonsoft.Json.Formatters; +using System.Threading; namespace Cuemon.Extensions.AspNetCore.Newtonsoft.Json { internal static class Bootstrapper { - private static readonly object PadLock = new(); + private static readonly Lock PadLock = new(); private static bool _initialized; internal static void Initialize() diff --git a/src/Cuemon.Extensions.AspNetCore.Text.Json/Bootstrapper.cs b/src/Cuemon.Extensions.AspNetCore.Text.Json/Bootstrapper.cs index 36c03f1ff..8e3fbc056 100644 --- a/src/Cuemon.Extensions.AspNetCore.Text.Json/Bootstrapper.cs +++ b/src/Cuemon.Extensions.AspNetCore.Text.Json/Bootstrapper.cs @@ -1,11 +1,12 @@ using Cuemon.Extensions.AspNetCore.Text.Json.Converters; using Cuemon.Extensions.Text.Json.Formatters; +using System.Threading; namespace Cuemon.Extensions.AspNetCore.Text.Json { internal static class Bootstrapper { - private static readonly object PadLock = new(); + private static readonly Lock PadLock = new(); private static bool _initialized; internal static void Initialize() diff --git a/src/Cuemon.Extensions.AspNetCore.Xml/Bootstrapper.cs b/src/Cuemon.Extensions.AspNetCore.Xml/Bootstrapper.cs index 700c9abf2..18459d657 100644 --- a/src/Cuemon.Extensions.AspNetCore.Xml/Bootstrapper.cs +++ b/src/Cuemon.Extensions.AspNetCore.Xml/Bootstrapper.cs @@ -1,11 +1,12 @@ using Cuemon.Extensions.AspNetCore.Xml.Converters; using Cuemon.Xml.Serialization.Formatters; +using System.Threading; namespace Cuemon.Extensions.AspNetCore.Xml { internal static class Bootstrapper { - private static readonly object PadLock = new(); + private static readonly Lock PadLock = new(); private static bool _initialized; internal static void Initialize() diff --git a/src/Cuemon.Extensions.Net/Http/SlimHttpClientFactory.cs b/src/Cuemon.Extensions.Net/Http/SlimHttpClientFactory.cs index 2e4c46d91..9450b69dc 100644 --- a/src/Cuemon.Extensions.Net/Http/SlimHttpClientFactory.cs +++ b/src/Cuemon.Extensions.Net/Http/SlimHttpClientFactory.cs @@ -29,7 +29,7 @@ public class SlimHttpClientFactory : IHttpClientFactory private readonly ConcurrentDictionary> _activeHandlers = new(); private readonly ConcurrentQueue _expiredHandlers = new(); private readonly Func _handlerFactory; - private readonly object _locker = new(); + private readonly Lock _locker = new(); private readonly SlimHttpClientFactoryOptions _options; internal static readonly TimeSpan ExpirationTimerDueTime = TimeSpan.FromSeconds(15); private Timer _expirationTimer; diff --git a/src/Cuemon.Extensions.Newtonsoft.Json/Formatters/NewtonsoftJsonFormatterOptions.cs b/src/Cuemon.Extensions.Newtonsoft.Json/Formatters/NewtonsoftJsonFormatterOptions.cs index 20934a28d..23e4291cd 100644 --- a/src/Cuemon.Extensions.Newtonsoft.Json/Formatters/NewtonsoftJsonFormatterOptions.cs +++ b/src/Cuemon.Extensions.Newtonsoft.Json/Formatters/NewtonsoftJsonFormatterOptions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Net.Http.Headers; using System.Reflection; +using System.Threading; using Cuemon.Configuration; using Cuemon.Diagnostics; using Cuemon.Extensions.Newtonsoft.Json.Converters; @@ -16,7 +17,7 @@ namespace Cuemon.Extensions.Newtonsoft.Json.Formatters /// public class NewtonsoftJsonFormatterOptions : IExceptionDescriptorOptions, IContentNegotiation, IValidatableParameterObject { - private readonly object _locker = new(); + private readonly Lock _locker = new(); private bool _refreshed; /// diff --git a/src/Cuemon.Extensions.Runtime.Caching/CacheEnumerableExtensions.cs b/src/Cuemon.Extensions.Runtime.Caching/CacheEnumerableExtensions.cs index 77eea4c55..26c52c673 100644 --- a/src/Cuemon.Extensions.Runtime.Caching/CacheEnumerableExtensions.cs +++ b/src/Cuemon.Extensions.Runtime.Caching/CacheEnumerableExtensions.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Reflection; +using System.Threading; using Cuemon.Collections.Generic; using Cuemon.Reflection; using Cuemon.Runtime; @@ -706,7 +707,7 @@ public static Func Memoize(ICacheEnumerable cache, string key, CacheInvalidation invalidation, FuncFactory valueFactory) where TTuple : Template { diff --git a/src/Cuemon.Extensions.Text.Json/Formatters/JsonFormatterOptions.cs b/src/Cuemon.Extensions.Text.Json/Formatters/JsonFormatterOptions.cs index 22b0bee87..be5e77dd9 100644 --- a/src/Cuemon.Extensions.Text.Json/Formatters/JsonFormatterOptions.cs +++ b/src/Cuemon.Extensions.Text.Json/Formatters/JsonFormatterOptions.cs @@ -4,6 +4,7 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; +using System.Threading; using Cuemon.Configuration; using Cuemon.Diagnostics; using Cuemon.Extensions.Text.Json.Converters; @@ -16,7 +17,7 @@ namespace Cuemon.Extensions.Text.Json.Formatters /// public class JsonFormatterOptions : IContentNegotiation, IExceptionDescriptorOptions, IValidatableParameterObject { - private readonly object _locker = new(); + private readonly Lock _locker = new(); private bool _refreshed; /// diff --git a/src/Cuemon.Extensions.YamlDotNet/Formatters/YamlFormatterOptions.cs b/src/Cuemon.Extensions.YamlDotNet/Formatters/YamlFormatterOptions.cs index d72a01faa..bf088163d 100644 --- a/src/Cuemon.Extensions.YamlDotNet/Formatters/YamlFormatterOptions.cs +++ b/src/Cuemon.Extensions.YamlDotNet/Formatters/YamlFormatterOptions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http.Headers; +using System.Threading; using Cuemon.Configuration; using Cuemon.Diagnostics; using Cuemon.Extensions.YamlDotNet.Converters; @@ -15,7 +16,7 @@ namespace Cuemon.Extensions.YamlDotNet.Formatters /// public class YamlFormatterOptions : EncodingOptions, IExceptionDescriptorOptions, IContentNegotiation, IValidatableParameterObject { - private readonly object _locker = new(); + private readonly Lock _locker = new(); private bool _refreshed; /// diff --git a/src/Cuemon.Xml/Serialization/Formatters/XmlFormatterOptions.cs b/src/Cuemon.Xml/Serialization/Formatters/XmlFormatterOptions.cs index 40624549c..270f750e3 100644 --- a/src/Cuemon.Xml/Serialization/Formatters/XmlFormatterOptions.cs +++ b/src/Cuemon.Xml/Serialization/Formatters/XmlFormatterOptions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net.Http.Headers; +using System.Threading; using Cuemon.Configuration; using Cuemon.Diagnostics; using Cuemon.Net.Http; @@ -13,7 +14,7 @@ namespace Cuemon.Xml.Serialization.Formatters /// public class XmlFormatterOptions : IExceptionDescriptorOptions, IContentNegotiation, IValidatableParameterObject { - private readonly object _locker = new(); + private readonly Lock _locker = new(); private bool _refreshed; /// From a1e6453868bbe8f36df0c51e200f7efe2bddbda7 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Mon, 26 Aug 2024 10:12:42 +0200 Subject: [PATCH 2/3] Dropped dependency to Backport.System.Threading.Lock. --- src/Cuemon.Core/Cuemon.Core.csproj | 4 +- src/Cuemon.Core/Threading/Lock.cs | 141 +++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 src/Cuemon.Core/Threading/Lock.cs diff --git a/src/Cuemon.Core/Cuemon.Core.csproj b/src/Cuemon.Core/Cuemon.Core.csproj index 65956938f..6807e66c6 100644 --- a/src/Cuemon.Core/Cuemon.Core.csproj +++ b/src/Cuemon.Core/Cuemon.Core.csproj @@ -13,8 +13,8 @@ action-factory bit-unit byte-unit binary-prefix decimal-prefix prefix-multiple multiple-table calculator configure configure-revert configure-exchange configurable condition options-pattern data-reader decorator delimited-string disposable finalize-disposable safe-invoke safe-invoke-async func-factory patterns reference-project clean-architecture clean-code task-action-factory task-func-factory template template-factory time-range time-unit validator guard text-encoding parser-factory security aes-cryptor cyclic-redundancy-check fowler-noll-vo-hash hash-factory hash-result hmac-message-digest hmac-secure-hash-algorithm keyed-crypto-hash keyed-crypto-algorithm message-digest non-crypto-algorithm secure-hash-algorithm unkeyed-crypto-hash - - + + diff --git a/src/Cuemon.Core/Threading/Lock.cs b/src/Cuemon.Core/Threading/Lock.cs new file mode 100644 index 000000000..e50b96901 --- /dev/null +++ b/src/Cuemon.Core/Threading/Lock.cs @@ -0,0 +1,141 @@ +// Credit: Mark Cilia Vincenti, 2024 +// Taken from: https://github.com/MarkCiliaVincenti/Backport.System.Threading.Lock +// NuGet package: https://www.nuget.org/packages/Backport.System.Threading.Lock + +using System.Runtime.CompilerServices; +using System.Security; +[assembly: SecurityTransparent()] +#if NET9_0_OR_GREATER +[assembly: TypeForwardedTo(typeof(System.Threading.Lock))] +#else +namespace System.Threading +{ + /// + /// A backport of .NET 9.0+'s System.Threading.Lock. Provides a way to get mutual exclusion in regions of code between different threads. + /// A lock may be held by one thread at a time. + /// + /// + /// Threads that cannot immediately enter the lock may wait for the lock to be exited or until a specified timeout. A thread + /// that holds a lock may enter the lock repeatedly without exiting it, such as recursively, in which case the thread should + /// eventually exit the lock the same number of times to fully exit the lock and allow other threads to enter the lock. + /// + public sealed class Lock + { +#pragma warning disable CS9216 // A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + /// + /// + /// + /// +#if !PRE_NETSTANDARD + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public void Enter() => Monitor.Enter(this); + + /// + /// + /// + /// + /// + /// + /// +#if !PRE_NETSTANDARD + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public bool TryEnter() => Monitor.TryEnter(this); + + /// + /// + /// + /// + /// + /// + /// + /// +#if !PRE_NETSTANDARD + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public bool TryEnter(TimeSpan timeout) => Monitor.TryEnter(this, timeout); + + /// + /// + /// + /// + /// + /// + /// + /// +#if !PRE_NETSTANDARD + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public bool TryEnter(int millisecondsTimeout) => Monitor.TryEnter(this, millisecondsTimeout); + + /// + /// + /// + /// + /// +#if !PRE_NETSTANDARD + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public void Exit() => Monitor.Exit(this); + + /// + /// Determines whether the current thread holds this lock. + /// + /// + /// true if the current thread holds this lock; otherwise, false. + /// + /// +#if !PRE_NETSTANDARD + public bool IsHeldByCurrentThread => Monitor.IsEntered(this); +#else + public bool IsHeldByCurrentThread => throw new NotSupportedException("IsHeldByCurrentThread is only supported on .NET Framework 4.5 or greater."); +#endif +#pragma warning restore CS9216 // A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + + /// + /// Enters the lock and returns a that may be disposed to exit the lock. Once the method returns, + /// the calling thread would be the only thread that holds the lock. This method is intended to be used along with a + /// language construct that would automatically dispose the , such as with the C# using statement. + /// + /// + /// A that may be disposed to exit the lock. + /// + /// + /// If the lock cannot be entered immediately, the calling thread waits for the lock to be exited. If the lock is + /// already held by the calling thread, the lock is entered again. The calling thread should exit the lock, such as by + /// disposing the returned , as many times as it had entered the lock to fully exit the lock and + /// allow other threads to enter the lock. + /// +#if !PRE_NETSTANDARD + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public Scope EnterScope() + { + Enter(); + return new Scope(this); + } + + /// + /// A disposable structure that is returned by , which when disposed, exits the lock. + /// + public ref struct Scope(Lock @lock) + { + /// + /// Exits the lock. + /// + /// + /// If the calling thread holds the lock multiple times, such as recursively, the lock is exited only once. The + /// calling thread should ensure that each enter is matched with an exit. + /// + /// + /// The calling thread does not hold the lock. + /// +#if !PRE_NETSTANDARD + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public readonly void Dispose() => @lock.Exit(); + } + } +} +#endif \ No newline at end of file From 1cafc5f598f4660e5f710c7f773794f19488a912 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Wed, 28 Aug 2024 00:55:04 +0200 Subject: [PATCH 3/3] Removed SecurityTransparent --- src/Cuemon.Core/Threading/Lock.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Cuemon.Core/Threading/Lock.cs b/src/Cuemon.Core/Threading/Lock.cs index e50b96901..2389f4ffc 100644 --- a/src/Cuemon.Core/Threading/Lock.cs +++ b/src/Cuemon.Core/Threading/Lock.cs @@ -4,7 +4,6 @@ using System.Runtime.CompilerServices; using System.Security; -[assembly: SecurityTransparent()] #if NET9_0_OR_GREATER [assembly: TypeForwardedTo(typeof(System.Threading.Lock))] #else @@ -138,4 +137,4 @@ public ref struct Scope(Lock @lock) } } } -#endif \ No newline at end of file +#endif