< Summary

Information
Class: CounterpointCollective.Threading.AsyncAutoResetEventSlim
Assembly: CounterpointCollective.Threading
File(s): /builds/counterpointcollective/prestoprimitives/Threading/AsyncAutoResetEventSlim.cs
Line coverage
76%
Covered lines: 38
Uncovered lines: 12
Coverable lines: 50
Total lines: 179
Line coverage: 76%
Branch coverage
68%
Covered branches: 11
Total branches: 16
Branch coverage: 68.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Set()100%22100%
get_IsSet()100%11100%
WaitOneAsync()50%44100%
WaitOneAsync()100%2271.42%
WaitOne()100%1166.66%
WaitOne(...)50%3242.85%
TransitionSetToNotSet()50%9433.33%
Terminate()100%22100%
get_Terminated()100%11100%
Reset()100%1160%

File(s)

/builds/counterpointcollective/prestoprimitives/Threading/AsyncAutoResetEventSlim.cs

#LineLine coverage
 1namespace CounterpointCollective.Threading
 2{
 3    /// <summary>
 4    /// Represents a lightweight, asynchronous auto-reset event.
 5    /// </summary>
 6    /// <remarks>
 7    /// <para>
 8    /// This class provides a thread-safe, asynchronous alternative to <see cref="AutoResetEvent"/>.
 9    /// It allows one waiting task to proceed when the event is set, and automatically resets after a single waiter cons
 10    /// </para>
 11    /// <para>
 12    /// Features include:
 13    /// <list type="bullet">
 14    /// <item>Async/await-friendly waiting via <see cref="WaitOneAsync(CancellationToken)"/>.</item>
 15    /// <item>Optional synchronous waiting via <see cref="WaitOne()"/> and overloads with timeout support.</item>
 16    /// <item>Termination support via <see cref="Terminate"/>, which cancels pending waiters and prevents future sets fr
 17    /// <item>Thread-safe querying of event state via <see cref="IsSet"/> and <see cref="Terminated"/>.</item>
 18    /// </list>
 19    /// </para>
 20    /// <para>
 21    /// Usage notes:
 22    /// <list type="bullet">
 23    /// <item>Calling <see cref="Set"/> signals the event to release a single waiting task.</item>
 24    /// <item>Once <see cref="Terminate"/> is called, all current and future waiters will throw <see cref="System.Object
 25    /// </list>
 26    /// </para>
 27    /// </remarks>
 828    public class AsyncAutoResetEventSlim(bool startSet)
 29    {
 30        private const int StateTerminated = -1;
 31        private const int StateNotSet = 0;
 32        private const int StateSet = 1;
 833        private int state = startSet ? StateSet : StateNotSet;
 34
 35        //Disposal is not compulsory because _sem.WaitHandle is not used.
 836        private readonly SemaphoreSlim _sem = new(startSet ? 1 : 0, 1);
 37
 38        //Disposal is not necessary because _cts.Register nor _cts.CancelAfter is used and _cts lives as long as this ob
 839        private readonly CancellationTokenSource _cts = new();
 40
 41        /// <summary>
 42        /// Sets the event, allowing a single waiter to proceed.Typically used to signal that a condition or resource is
 43        /// for one waiting task.
 44        /// </summary>
 45        /// <remarks>
 46        /// <list type="bullet">
 47        /// <item>If the event is already set, this method does nothing.</item>
 48        /// <item>If the event has been terminated via <see cref="Terminate"/>, calling this method has no effect.</item
 49        /// </list>
 50        /// </remarks>
 51
 52        public void Set()
 53        {
 954            if (Interlocked.CompareExchange(ref state, StateSet, StateNotSet) == StateNotSet)
 55            {
 956                _ = _sem.Release();
 57            }
 958        }
 59
 60        /// <summary>
 61        /// Gets a value indicating whether the event is currently set.
 62        /// <list type="bullet">
 63        /// <item><c>true</c> if the event is set and has not yet been consumed by a waiter.</item>
 64        /// <item><c>false</c> if the event is not set or if it has been terminated via <see cref="Terminate"/>.</item>
 65        /// </list>
 66        /// This property is thread-safe but does not reserve the event; a concurrent <see cref="WaitOneAsync(Cancellati
 67        /// </summary>
 468        public bool IsSet => Volatile.Read(ref state) == 1;
 69
 70        public async Task WaitOneAsync(CancellationToken cancellationToken = default)
 71        {
 972            using var linkedCts = cancellationToken == default
 973                ? null
 974                : CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cts.Token);
 75
 976            cancellationToken = linkedCts?.Token ?? _cts.Token;
 77
 78            try
 79            {
 980                await _sem.WaitAsync(cancellationToken);
 781            }
 282            catch (OperationCanceledException) when (_cts.IsCancellationRequested)
 83            {
 284                throw new ObjectDisposedException(nameof(AsyncAutoResetEventSlim), "This event has been terminated.");
 85            }
 86
 787            TransitionSetToNotSet();
 788        }
 89
 90        public async Task<bool> WaitOneAsync(TimeSpan t)
 91        {
 92            try
 93            {
 294                if (await _sem.WaitAsync(t, _cts.Token))
 95                {
 196                    TransitionSetToNotSet();
 197                    return true;
 98                }
 99                else
 100                {
 1101                    return false;
 102                }
 103            }
 0104            catch (OperationCanceledException) when (_cts.IsCancellationRequested)
 105            {
 0106                throw new ObjectDisposedException(nameof(AsyncAutoResetEventSlim), "This event has been terminated.");
 107            }
 2108        }
 109
 110        public void WaitOne()
 111        {
 112            try
 113            {
 1114                _sem.Wait(_cts.Token);
 1115                TransitionSetToNotSet();
 1116            }
 0117            catch (OperationCanceledException) when (_cts.IsCancellationRequested)
 118            {
 0119                throw new ObjectDisposedException(nameof(AsyncAutoResetEventSlim), "This event has been terminated.");
 120            }
 1121        }
 122
 123        public bool WaitOne(TimeSpan t)
 124        {
 125            try
 126            {
 1127                if (_sem.Wait(t, _cts.Token))
 128                {
 0129                    TransitionSetToNotSet();
 0130                    return true;
 131                }
 132                else
 133                {
 1134                    return false;
 135                }
 136            }
 0137            catch (OperationCanceledException) when (_cts.IsCancellationRequested)
 138            {
 0139                throw new ObjectDisposedException(nameof(AsyncAutoResetEventSlim), "This event has been terminated.");
 140            }
 1141        }
 142
 143        private void TransitionSetToNotSet()
 144        {
 9145            var prevState = Interlocked.CompareExchange(ref state, StateNotSet, StateSet);
 146            switch (prevState)
 147            {
 148                case StateTerminated:
 0149                    throw new ObjectDisposedException(nameof(AsyncAutoResetEventSlim), "This event has been terminated."
 150                case StateNotSet:
 0151                    throw new InvalidOperationException("The event was not set. This cannot happen.");
 152                default:
 153                    break;
 154            }
 155        }
 156
 157        public void Terminate()
 158        {
 2159            if (Interlocked.Exchange(ref state, StateTerminated) != StateTerminated)
 160            {
 2161                _cts.Cancel();
 162            }
 2163        }
 164
 2165        public bool Terminated => Volatile.Read(ref state) == StateTerminated;
 166
 167        public void Reset()
 168        {
 169            try
 170            {
 1171                WaitOne(TimeSpan.Zero);
 1172            }
 0173            catch (ObjectDisposedException)
 174            {
 175                // Ignore
 0176            }
 1177        }
 178    }
 179}