diff --git a/src/THNETII.Common/Threading/SemaphoreSlimContext.cs b/src/THNETII.Common/Threading/SemaphoreSlimContext.cs
new file mode 100644
index 00000000..27a87b63
--- /dev/null
+++ b/src/THNETII.Common/Threading/SemaphoreSlimContext.cs
@@ -0,0 +1,459 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace THNETII.Common.Threading
+{
+ ///
+ /// A disposable context token that can be used to track the lifetime during
+ /// which an application holds access to a
+ /// instance.
+ ///
+ /// When the context is disposed (or garbage collected), the
+ /// method on the original
+ /// instance is invoked.
+ ///
+ ///
+ ///
+ /// Using this class allows developers to wrap a critical section protected
+ /// by a semaphore in a using code-block. This guarantees for safe
+ /// resource management similar to the lock code-block. However, in
+ /// addition to the lock block, the
+ /// class also supports asynchronous locking through its Async-methods.
+ ///
+ public class SemaphoreSlimContext : IDisposable
+ {
+ private readonly SemaphoreSlim semaphore;
+
+ ///
+ /// Gets the number of times the object
+ /// that is captured by the current instance should be released.
+ ///
+ /// An value greater than 0 (zero).
+ ///
+ /// The returned value corresponds to the value that is passed as the
+ /// argument value to when
+ /// is invoked.
+ ///
+ protected int ReleaseCount { get; }
+
+ private SemaphoreSlimContext(SemaphoreSlim semaphore, int releaseCount = 1, bool verify = false)
+ {
+ Debug.Assert(!verify, nameof(verify) + " is not false.");
+ this.semaphore = semaphore;
+ ReleaseCount = releaseCount;
+ }
+
+ ///
+ /// Creates a new semaphore context for the specified and already
+ /// acquired instance.
+ ///
+ /// The instance that has been acquired by the application.
+ /// The number of times the semaphore has been acquired. Defaults to 1.
+ /// is null.
+ /// is less than 1.
+ protected SemaphoreSlimContext(SemaphoreSlim semaphore,
+ int releaseCount = 1)
+ : this(
+ semaphore ?? throw new ArgumentNullException(nameof(semaphore)),
+ releaseCount >= 1 ? releaseCount : throw new ArgumentOutOfRangeException(nameof(releaseCount), releaseCount, nameof(releaseCount) + " is less than 1"),
+ verify: false
+ )
+ { }
+
+ internal static SemaphoreSlimContext CreateNoVerify(SemaphoreSlim semaphore,
+ int releaseCount = 1) =>
+ new SemaphoreSlimContext(semaphore, releaseCount, verify: false);
+
+ ///
+ /// Blocks the current thread until it can enter the
+ /// and returns a context object that relases the semaphore when it is
+ /// disposed.
+ ///
+ /// The semaphore to enter.
+ ///
+ /// A instance that captures
+ /// and releases the semaphore once its
+ /// method is invoked.
+ ///
+ /// is null.
+ /// instance has been disposed.
+ public static SemaphoreSlimContext CreateOnEnter(SemaphoreSlim semaphore)
+ {
+ semaphore.ThrowIfNull(nameof(semaphore)).Wait();
+ return new SemaphoreSlimContext(semaphore, verify: false);
+ }
+
+ ///
+ /// Blocks the current thread until it can enter the ,
+ /// while observing a
+ /// and returns a context object that relases the semaphore when it is
+ /// disposed.
+ ///
+ /// The semaphore to enter.
+ /// The token to observe.
+ ///
+ /// A instance that captures
+ /// and releases the semaphore once its
+ /// method is invoked.
+ ///
+ /// is null.
+ /// was canceled.
+ ///
+ /// instance has been disposed.
+ /// -or-
+ /// The that created has already been disposed.
+ ///
+ public static SemaphoreSlimContext CreateOnEnter(SemaphoreSlim semaphore,
+ CancellationToken cancelToken)
+ {
+ semaphore.ThrowIfNull(nameof(semaphore)).Wait(cancelToken);
+ return new SemaphoreSlimContext(semaphore, verify: false);
+ }
+
+ ///
+ /// Blocks the current thread until it can enter the ,
+ /// using a 32-bit integer that specifies the timeout
+ /// and returns a context object that relases the semaphore when it is
+ /// disposed.
+ ///
+ /// The semaphore to enter.
+ /// The number of milliseconds to wait, or (-1) to wait indefinitely.
+ ///
+ /// null if the timeout expired before the semphore could be entered; otherwise,
+ /// a instance that captures
+ /// and releases the semaphore once its
+ /// method is invoked.
+ ///
+ /// is null.
+ /// is a negative value other than -1, which represents an infinite time-out.
+ /// instance has been disposed.
+ public static SemaphoreSlimContext CreateOnEnter(SemaphoreSlim semaphore,
+ int millisecondsTimeout)
+ {
+ bool entered = semaphore.ThrowIfNull(nameof(semaphore))
+ .Wait(millisecondsTimeout);
+ return entered
+ ? new SemaphoreSlimContext(semaphore, verify: false)
+ : null;
+ }
+
+ ///
+ /// Blocks the current thread until it can enter the ,
+ /// using a 32-bit integer that specifies the timeout, while observing a
+ /// and returns a context object that relases the semaphore when it is
+ /// disposed.
+ ///
+ /// The semaphore to enter.
+ /// The number of milliseconds to wait, or (-1) to wait indefinitely.
+ /// The token to observe.
+ ///
+ /// null if the timeout expired before the semphore could be entered; otherwise,
+ /// a instance that captures
+ /// and releases the semaphore once its
+ /// method is invoked.
+ ///
+ /// is null.
+ /// is a negative value other than -1, which represents an infinite time-out.
+ /// was canceled.
+ ///
+ /// instance has been disposed.
+ /// -or-
+ /// The that created has already been disposed.
+ ///
+ public static SemaphoreSlimContext CreateOnEnter(SemaphoreSlim semaphore,
+ int millisecondsTimeout, CancellationToken cancelToken)
+ {
+ bool entered = semaphore.ThrowIfNull(nameof(semaphore))
+ .Wait(millisecondsTimeout, cancelToken);
+ return entered
+ ? new SemaphoreSlimContext(semaphore, verify: false)
+ : null;
+ }
+
+ ///
+ /// Blocks the current thread until it can enter the ,
+ /// using the specified timeout
+ /// and returns a context object that relases the semaphore when it is
+ /// disposed.
+ ///
+ /// The semaphore to enter.
+ /// A value that represents time to wait, or a that represents -1 milliseconds to wait indefinitely.
+ ///
+ /// null if the timeout expired before the semphore could be entered; otherwise,
+ /// a instance that captures
+ /// and releases the semaphore once its
+ /// method is invoked.
+ ///
+ /// is null.
+ /// is a negative value other than -1 milliseconds, which represents an infinite time-out.
+ /// instance has been disposed.
+ public static SemaphoreSlimContext CreateOnEnter(SemaphoreSlim semaphore,
+ TimeSpan timeout)
+ {
+ bool entered = semaphore.ThrowIfNull(nameof(semaphore))
+ .Wait(timeout);
+ return entered
+ ? new SemaphoreSlimContext(semaphore, verify: false)
+ : null;
+ }
+
+ ///
+ /// Blocks the current thread until it can enter the ,
+ /// using the specified timeout while observing a
+ /// and returns a context object that relases the semaphore when it is
+ /// disposed.
+ ///
+ /// The semaphore to enter.
+ /// A value that represents time to wait, or a that represents -1 milliseconds to wait indefinitely.
+ /// The token to observe.
+ ///
+ /// null if the timeout expired before the semphore could be entered; otherwise,
+ /// a instance that captures
+ /// and releases the semaphore once its
+ /// method is invoked.
+ ///
+ /// is null.
+ /// is a negative value other than -1 milliseconds, which represents an infinite time-out.
+ /// was canceled.
+ ///
+ /// instance has been disposed.
+ /// -or-
+ /// The that created has already been disposed.
+ ///
+ public static SemaphoreSlimContext CreateOnEnter(SemaphoreSlim semaphore,
+ TimeSpan timeout, CancellationToken cancelToken)
+ {
+ bool entered = semaphore.ThrowIfNull(nameof(semaphore))
+ .Wait(timeout, cancelToken);
+ return entered
+ ? new SemaphoreSlimContext(semaphore, verify: false)
+ : null;
+ }
+
+ ///
+ /// Asynchronously waits to enter the and
+ /// returns a context object that releases the semaphore when it is
+ /// disposed.
+ ///
+ /// The semaphore to enter.
+ /// A task that will complete when the semaphore has been entered.
+ /// is null.
+ /// has already been disposed.
+ public static async Task CreateOnEnterAsync(
+ SemaphoreSlim semaphore)
+ {
+ await semaphore.ThrowIfNull(nameof(semaphore)).WaitAsync()
+ .ConfigureAwait(continueOnCapturedContext: false);
+ return new SemaphoreSlimContext(semaphore, verify: false);
+ }
+
+ ///
+ /// Asynchronously waits to enter a ,
+ /// while observing a and
+ /// returns a context object that releases the semaphore when it is
+ /// disposed.
+ ///
+ /// A task that will complete when the semaphore has been entered.
+ /// The semaphore to enter.
+ /// The token to observe.
+ /// is null.
+ ///
+ /// instance has been disposed.
+ /// -or-
+ /// The that created has already been disposed.
+ ///
+ /// was canceled.
+ public static async Task CreateOnEnterAsync(
+ SemaphoreSlim semaphore, CancellationToken cancelToken)
+ {
+ await semaphore.ThrowIfNull(nameof(semaphore)).WaitAsync(cancelToken)
+ .ConfigureAwait(continueOnCapturedContext: false);
+ return new SemaphoreSlimContext(semaphore, verify: false);
+ }
+
+ ///
+ /// Asynchronously waits to enter a ,
+ /// using a 32-bit signed integer to measure the time interval and
+ /// returns a context object that releases the semaphore when it is
+ /// disposed.
+ ///
+ ///
+ /// A task that will complete with a result of a context object that
+ /// releases the semaphore when disposed if the current thread
+ /// successfully entered the ,
+ /// otherwise with a result of null.
+ /// The semaphore to enter.
+ /// The number of milliseconds to wait, or (-1) to wait indefinitely.
+ /// is null.
+ /// is a negative number other than -1, which represents an infinite time-out.
+ /// has already been disposed.
+ public static async Task CreateOnEnterAsync(
+ SemaphoreSlim semaphore, int millisecondsTimeout)
+ {
+ bool entered = await semaphore.ThrowIfNull(nameof(semaphore))
+ .WaitAsync(millisecondsTimeout)
+ .ConfigureAwait(continueOnCapturedContext: false);
+ return entered
+ ? new SemaphoreSlimContext(semaphore, verify: false)
+ : null;
+ }
+
+ ///
+ /// Asynchronously waits to enter a ,
+ /// using a 32-bit signed integer to measure the time interval
+ /// while observing a and
+ /// returns a context object that releases the semaphore when it is
+ /// disposed.
+ ///
+ ///
+ /// A task that will complete with a result of a context object that
+ /// releases the semaphore when disposed if the current thread
+ /// successfully entered the ,
+ /// otherwise with a result of null.
+ /// The semaphore to enter.
+ /// The number of milliseconds to wait, or (-1) to wait indefinitely.
+ /// The token to observe.
+ /// is null.
+ /// is a negative number other than -1, which represents an infinite time-out.
+ ///
+ /// instance has been disposed.
+ /// -or-
+ /// The that created has already been disposed.
+ ///
+ /// was canceled.
+ public static async Task CreateOnEnterAsync(
+ SemaphoreSlim semaphore, int millisecondsTimeout,
+ CancellationToken cancelToken)
+ {
+ bool entered = await semaphore.ThrowIfNull(nameof(semaphore))
+ .WaitAsync(millisecondsTimeout, cancelToken)
+ .ConfigureAwait(continueOnCapturedContext: false);
+ return entered
+ ? new SemaphoreSlimContext(semaphore, verify: false)
+ : null;
+ }
+
+ ///
+ /// Asynchronously waits to enter a ,
+ /// using the specified timeout and
+ /// returns a context object that releases the semaphore when it is
+ /// disposed.
+ ///
+ ///
+ /// A task that will complete with a result of a context object that
+ /// releases the semaphore when disposed if the current thread
+ /// successfully entered the ,
+ /// otherwise with a result of null.
+ /// The semaphore to enter.
+ /// The time to wait, or a value representing -1 milliseconds to wait indefinitely.
+ /// is null.
+ /// is a negative value other than -1 milliseconds, which represents an infinite time-out.
+ /// has already been disposed.
+ public static async Task CreateOnEnterAsync(
+ SemaphoreSlim semaphore, TimeSpan timeout)
+ {
+ bool entered = await semaphore.ThrowIfNull(nameof(semaphore))
+ .WaitAsync(timeout)
+ .ConfigureAwait(continueOnCapturedContext: false);
+ return entered
+ ? new SemaphoreSlimContext(semaphore, verify: false)
+ : null;
+ }
+
+ ///
+ /// Asynchronously waits to enter a ,
+ /// using the specified timeout and
+ /// while observing a and
+ /// returns a context object that releases the semaphore when it is
+ /// disposed.
+ ///
+ ///
+ /// A task that will complete with a result of a context object that
+ /// releases the semaphore when disposed if the current thread
+ /// successfully entered the ,
+ /// otherwise with a result of null.
+ /// The semaphore to enter.
+ /// The time to wait, or a value representing -1 milliseconds to wait indefinitely.
+ /// The token to observe.
+ /// is null.
+ /// is a negative value other than -1 milliseconds, which represents an infinite time-out.
+ ///
+ /// instance has been disposed.
+ /// -or-
+ /// The that created has already been disposed.
+ ///
+ /// was canceled.
+ public static async Task CreateOnEnterAsync(
+ SemaphoreSlim semaphore, TimeSpan timeout,
+ CancellationToken cancelToken)
+ {
+ bool entered = await semaphore.ThrowIfNull(nameof(semaphore))
+ .WaitAsync(timeout, cancelToken)
+ .ConfigureAwait(continueOnCapturedContext: false);
+ return entered
+ ? new SemaphoreSlimContext(semaphore, verify: false)
+ : null;
+ }
+
+ #region IDisposable Support
+ private int disposedValue = 0; // To detect redundant calls
+
+ ///
+ /// Releases the captured semaphore by calling
+ /// with
+ /// as the argument value.
+ ///
+ /// This method is thread-safe and guaranteed to only release the
+ /// semaphore exactly once during the lifetime of this context instance.
+ ///
+ ///
+ ///
+ /// true if called from within an implementation of
+ /// , false if called from
+ /// within the finalizer.
+ ///
+ ///
+ /// The method atomically flips a state flag variable when the method is
+ /// executed the first time. Thus, subsequent calls to
+ /// have no effect.
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (Interlocked.Exchange(ref disposedValue, 1) == 0)
+ {
+ semaphore?.Release(ReleaseCount);
+ }
+ }
+
+ ///
+ /// Releases the semaphore instance.
+ ///
+ ///
+ /// This method is thread-safe. The semaphore is only released once.
+ /// After the initial call to , subsequent calls
+ /// have no effect.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Releases the semaphore object captured by this instance when this is
+ /// instance is garbage collected.
+ ///
+ ///
+ /// Applications should not rely on garbage collection to properly
+ /// release held semaphore objects. Instead the current context instance
+ /// should be used in a using code-block.
+ ///
+ ~SemaphoreSlimContext()
+ {
+ Dispose(false);
+ }
+ #endregion
+ }
+}
diff --git a/src/THNETII.Common/Threading/SemaphoreSlimExtensions.cs b/src/THNETII.Common/Threading/SemaphoreSlimExtensions.cs
new file mode 100644
index 00000000..6ba75df0
--- /dev/null
+++ b/src/THNETII.Common/Threading/SemaphoreSlimExtensions.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace THNETII.Common.Threading
+{
+ ///
+ /// Provides extension methods for the class.
+ ///
+ public static class SemaphoreSlimExtensions
+ {
+ ///
+ public static SemaphoreSlimContext WaitContext(this SemaphoreSlim semaphore) =>
+ SemaphoreSlimContext.CreateOnEnter(semaphore);
+
+ ///
+ public static SemaphoreSlimContext WaitContext(this SemaphoreSlim semaphore,
+ int millisecondsTimeout) =>
+ SemaphoreSlimContext.CreateOnEnter(semaphore, millisecondsTimeout);
+
+ ///
+ public static SemaphoreSlimContext WaitContext(this SemaphoreSlim semaphore,
+ TimeSpan timeout) =>
+ SemaphoreSlimContext.CreateOnEnter(semaphore, timeout);
+
+ ///
+ public static SemaphoreSlimContext WaitContext(this SemaphoreSlim semaphore,
+ CancellationToken cancelToken) =>
+ SemaphoreSlimContext.CreateOnEnter(semaphore, cancelToken);
+
+ ///
+ public static SemaphoreSlimContext WaitContext(this SemaphoreSlim semaphore,
+ int millisecondsTimeout, CancellationToken cancelToken) =>
+ SemaphoreSlimContext.CreateOnEnter(semaphore, millisecondsTimeout, cancelToken);
+
+ ///
+ public static SemaphoreSlimContext WaitContext(this SemaphoreSlim semaphore,
+ TimeSpan timeout, CancellationToken cancelToken) =>
+ SemaphoreSlimContext.CreateOnEnter(semaphore, timeout, cancelToken);
+
+ ///
+ public static Task WaitContextAsync(
+ this SemaphoreSlim semaphore) =>
+ SemaphoreSlimContext.CreateOnEnterAsync(semaphore);
+
+ ///
+ public static Task WaitContextAsync(
+ this SemaphoreSlim semaphore, int millisecondsTimeout) =>
+ SemaphoreSlimContext.CreateOnEnterAsync(semaphore, millisecondsTimeout);
+
+ ///
+ public static Task WaitContextAsync(
+ this SemaphoreSlim semaphore, TimeSpan timeout) =>
+ SemaphoreSlimContext.CreateOnEnterAsync(semaphore, timeout);
+
+ ///
+ public static Task WaitContextAsync(
+ this SemaphoreSlim semaphore, CancellationToken cancelToken) =>
+ SemaphoreSlimContext.CreateOnEnterAsync(semaphore, cancelToken);
+
+ ///
+ public static Task WaitContextAsync(
+ this SemaphoreSlim semaphore, int millisecondsTimeout,
+ CancellationToken cancelToken) =>
+ SemaphoreSlimContext.CreateOnEnterAsync(semaphore, millisecondsTimeout, cancelToken);
+
+ ///
+ public static Task WaitContextAsync(
+ this SemaphoreSlim semaphore, TimeSpan timeout,
+ CancellationToken cancelToken) =>
+ SemaphoreSlimContext.CreateOnEnterAsync(semaphore, timeout, cancelToken);
+ }
+}