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..6807e66c6 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.Core/Threading/Lock.cs b/src/Cuemon.Core/Threading/Lock.cs
new file mode 100644
index 000000000..2389f4ffc
--- /dev/null
+++ b/src/Cuemon.Core/Threading/Lock.cs
@@ -0,0 +1,140 @@
+// 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;
+#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
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 60cda82c9..9f9ed8813 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 50f33939b..8da6adf94 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 4da21b799..c21d48bbd 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 014f69640..62a62313b 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 58fcc9933..2826e3559 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 256a68360..fe01097d1 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;
///