private async Task TakeReadLockCoreAsync(SemaphoreSlim objCurrentSemaphore, SafeWriterSemaphoreRelease objRelease, CancellationToken token = default)
        {
            // Temporarily acquiring a write lock just to mess with the read locks is a bottleneck, so don't do any such setting unless we need it
            bool blnDoWriterLock = _objTopLevelWriterSemaphore.CurrentCount == 0;

            if (blnDoWriterLock)
            {
                await objCurrentSemaphore.WaitAsync(token).ConfigureAwait(false);
            }
            try
            {
                if (Interlocked.Increment(ref _intCountActiveReaders) == 1)
                {
                    try
                    {
                        await _objReaderSemaphore.WaitAsync(token).ConfigureAwait(false);
                    }
                    catch
                    {
                        Interlocked.Decrement(ref _intCountActiveReaders);
                        throw;
                    }
                }
            }
            finally
            {
                // Deliberately DoRelease to not decrement the active reader count
                await objRelease.DoReleaseAsync(blnDoWriterLock).ConfigureAwait(false);
            }
        }
        /// <summary>
        /// Try to synchronously obtain a lock for writing.
        /// The returned SafeSemaphoreWriterRelease must be stored for when the write lock is to be released.
        /// </summary>
        public IDisposable EnterWriteLock(CancellationToken token = default)
        {
            if (_blnIsDisposed || _blnIsDisposing)
            {
                throw new ObjectDisposedException(nameof(AsyncFriendlyReaderWriterLock));
            }

            SemaphoreSlim objCurrentSemaphore = _objCurrentWriterSemaphore.Value ?? _objTopLevelWriterSemaphore;
            SemaphoreSlim objNextSemaphore    = Utils.SemaphorePool.Get();

            _objCurrentWriterSemaphore.Value = objNextSemaphore;
            SafeWriterSemaphoreRelease objReturn = new SafeWriterSemaphoreRelease(objCurrentSemaphore, objNextSemaphore, this);

            try
            {
                if (Utils.EverDoEvents)
                {
                    while (!objCurrentSemaphore.Wait(Utils.DefaultSleepDuration, token))
                    {
                        Utils.DoEventsSafe();
                    }
                }
                else
                {
                    objCurrentSemaphore.Wait(token);
                }
                if (Interlocked.Increment(ref _intCountActiveReaders) == 1 && !IsWriteLockHeldRecursively)
                {
                    try
                    {
                        if (Utils.EverDoEvents)
                        {
                            while (!_objReaderSemaphore.Wait(Utils.DefaultSleepDuration, token))
                            {
                                Utils.DoEventsSafe();
                            }
                        }
                        else
                        {
                            _objReaderSemaphore.Wait(token);
                        }
                    }
                    catch
                    {
                        Interlocked.Decrement(ref _intCountActiveReaders);
                        throw;
                    }
                }
            }
            catch
            {
                objReturn.Dispose();
                throw;
            }
            return(objReturn);
        }
        /// <summary>
        /// Try to asynchronously obtain a lock for reading.
        /// </summary>
        public Task EnterReadLockAsync(CancellationToken token = default)
        {
            if (_blnIsDisposed || _blnIsDisposing)
            {
                return(Task.FromException(new ObjectDisposedException(nameof(AsyncFriendlyReaderWriterLock))));
            }
            SemaphoreSlim objCurrentSemaphore = _objCurrentWriterSemaphore.Value ?? _objTopLevelWriterSemaphore;
            SemaphoreSlim objNextSemaphore    = Utils.SemaphorePool.Get();

            _objCurrentWriterSemaphore.Value = objNextSemaphore;
            SafeWriterSemaphoreRelease objRelease = new SafeWriterSemaphoreRelease(objCurrentSemaphore, objNextSemaphore, this);

            return(TakeReadLockCoreAsync(objCurrentSemaphore, objRelease, token));
        }
        /// <summary>
        /// Try to asynchronously obtain a lock for writing.
        /// The returned SafeSemaphoreWriterRelease must be stored for when the write lock is to be released.
        /// </summary>
        public Task <IAsyncDisposable> EnterWriteLockAsync()
        {
            if (_intDisposedStatus != 0)
            {
                return(Task.FromException <IAsyncDisposable>(new ObjectDisposedException(nameof(AsyncFriendlyReaderWriterLock))));
            }
            (DebuggableSemaphoreSlim objLastSemaphore, DebuggableSemaphoreSlim objCurrentSemaphore)
                = _objCurrentWriterSemaphore.Value
                  ?? new Tuple <DebuggableSemaphoreSlim, DebuggableSemaphoreSlim>(null, _objTopLevelWriterSemaphore);
            DebuggableSemaphoreSlim objNextSemaphore = Utils.SemaphorePool.Get();

            // Extremely hacky solution to buggy semaphore (re)cycling in AsyncLocal
            // TODO: Fix this properly. The problem is that after an AsyncLocal shallow-copy in a different context, the semaphores can get returned in the copy without altering the original AsyncLocal
            while (objNextSemaphore == objCurrentSemaphore || objNextSemaphore == objLastSemaphore)
            {
                objNextSemaphore = Utils.SemaphorePool.Get();
            }
            _objCurrentWriterSemaphore.Value = new Tuple <DebuggableSemaphoreSlim, DebuggableSemaphoreSlim>(objCurrentSemaphore, objNextSemaphore);
            SafeWriterSemaphoreRelease objRelease = new SafeWriterSemaphoreRelease(objLastSemaphore, objCurrentSemaphore, objNextSemaphore, this);

            return(TakeWriteLockCoreAsync(objCurrentSemaphore, objRelease));
        }
        /// <summary>
        /// Try to synchronously obtain a lock for reading.
        /// </summary>
        public void EnterReadLock(CancellationToken token = default)
        {
            if (_blnIsDisposed || _blnIsDisposing)
            {
                throw new ObjectDisposedException(nameof(AsyncFriendlyReaderWriterLock));
            }

            // Temporarily acquiring a write lock just to mess with the read locks is a bottleneck, so don't do any such setting unless we need it
            bool          blnDoWriterLock     = false;
            SemaphoreSlim objCurrentSemaphore = _objCurrentWriterSemaphore.Value ?? _objTopLevelWriterSemaphore;
            SemaphoreSlim objNextSemaphore    = Utils.SemaphorePool.Get();

            _objCurrentWriterSemaphore.Value = objNextSemaphore;
            SafeWriterSemaphoreRelease objRelease = new SafeWriterSemaphoreRelease(objCurrentSemaphore, objNextSemaphore, this);

            try
            {
                blnDoWriterLock = _objTopLevelWriterSemaphore.CurrentCount == 0;
                if (blnDoWriterLock)
                {
                    if (Utils.EverDoEvents)
                    {
                        while (!objCurrentSemaphore.Wait(Utils.DefaultSleepDuration, token))
                        {
                            Utils.DoEventsSafe();
                        }
                    }
                    else
                    {
                        objCurrentSemaphore.Wait(token);
                    }
                }

                if (Interlocked.Increment(ref _intCountActiveReaders) == 1)
                {
                    try
                    {
                        if (Utils.EverDoEvents)
                        {
                            while (!_objReaderSemaphore.Wait(Utils.DefaultSleepDuration, token))
                            {
                                Utils.DoEventsSafe();
                            }
                        }
                        else
                        {
                            _objReaderSemaphore.Wait(token);
                        }
                    }
                    catch
                    {
                        Interlocked.Decrement(ref _intCountActiveReaders);
                        throw;
                    }
                }
            }
            finally
            {
                // Deliberately DoRelease to not decrement the active reader count
                objRelease.DoRelease(blnDoWriterLock);
            }
        }
        private async Task <IAsyncDisposable> TakeWriteLockCoreAsync(SemaphoreSlim objCurrentSemaphore, SafeWriterSemaphoreRelease objRelease, CancellationToken token)
        {
            try
            {
                await objCurrentSemaphore.WaitAsync(token).ConfigureAwait(false);

                if (Interlocked.Increment(ref _intCountActiveReaders) == 1 && !IsWriteLockHeldRecursively)
                {
                    try
                    {
                        await _objReaderSemaphore.WaitAsync(token).ConfigureAwait(false);
                    }
                    catch
                    {
                        Interlocked.Decrement(ref _intCountActiveReaders);
                        throw;
                    }
                }
            }
            catch
            {
                await objRelease.DisposeAsync().ConfigureAwait(false);

                throw;
            }
            return(objRelease);
        }
        /// <summary>
        /// Try to synchronously obtain a lock for writing.
        /// The returned SafeSemaphoreWriterRelease must be stored for when the write lock is to be released.
        /// </summary>
        public IDisposable EnterWriteLock(CancellationToken token = default)
        {
            if (_intDisposedStatus != 0)
            {
                throw new ObjectDisposedException(nameof(AsyncFriendlyReaderWriterLock));
            }

            token.ThrowIfCancellationRequested();
            (DebuggableSemaphoreSlim objLastSemaphore, DebuggableSemaphoreSlim objCurrentSemaphore)
                = _objCurrentWriterSemaphore.Value
                  ?? new Tuple <DebuggableSemaphoreSlim, DebuggableSemaphoreSlim>(null, _objTopLevelWriterSemaphore);
            DebuggableSemaphoreSlim objNextSemaphore = Utils.SemaphorePool.Get();

            // Extremely hacky solution to buggy semaphore (re)cycling in AsyncLocal
            // TODO: Fix this properly. The problem is that after an AsyncLocal shallow-copy in a different context, the semaphores can get returned in the copy without altering the original AsyncLocal
            while (objNextSemaphore == objCurrentSemaphore || objNextSemaphore == objLastSemaphore)
            {
                objNextSemaphore = Utils.SemaphorePool.Get();
            }
            _objCurrentWriterSemaphore.Value = new Tuple <DebuggableSemaphoreSlim, DebuggableSemaphoreSlim>(objCurrentSemaphore, objNextSemaphore);
            SafeWriterSemaphoreRelease objRelease = new SafeWriterSemaphoreRelease(objLastSemaphore, objCurrentSemaphore, objNextSemaphore, this);

            try
            {
                objCurrentSemaphore.SafeWait(token);
            }
            catch
            {
                _objCurrentWriterSemaphore.Value = new Tuple <DebuggableSemaphoreSlim, DebuggableSemaphoreSlim>(objLastSemaphore, objCurrentSemaphore);
                Utils.SemaphorePool.Return(ref objNextSemaphore);
                throw;
            }
            try
            {
                if (Interlocked.Increment(ref _intCountActiveReaders) == 1)
                {
                    try
                    {
                        // Wait for the reader lock only if there have been no other write locks before us
                        if (_objTopLevelWriterSemaphore.CurrentCount != 0 || objCurrentSemaphore == _objTopLevelWriterSemaphore)
                        {
                            _objReaderSemaphore.SafeWait(token);
                        }
                    }
                    catch
                    {
                        Interlocked.Decrement(ref _intCountActiveReaders);
                        throw;
                    }
                }
            }
            catch
            {
                _objCurrentWriterSemaphore.Value = new Tuple <DebuggableSemaphoreSlim, DebuggableSemaphoreSlim>(objLastSemaphore, objCurrentSemaphore);
                try
                {
                    // ReSharper disable once MethodSupportsCancellation
                    objNextSemaphore.SafeWait();
                    try
                    {
                        objCurrentSemaphore.Release();
                    }
                    finally
                    {
                        objNextSemaphore.Release();
                    }
                }
                finally
                {
                    Utils.SemaphorePool.Return(ref objNextSemaphore);
                }
                throw;
            }
            return(objRelease);
        }
        private async Task <IAsyncDisposable> TakeWriteLockCoreAsync(DebuggableSemaphoreSlim objCurrentSemaphore, SafeWriterSemaphoreRelease objRelease, CancellationToken token)
        {
            try
            {
                await objCurrentSemaphore.WaitAsync(token);

                if (Interlocked.Increment(ref _intCountActiveReaders) == 1)
                {
                    try
                    {
                        // Wait for the reader lock only if there have been no other write locks before us
                        if (_objTopLevelWriterSemaphore.CurrentCount != 0 || objCurrentSemaphore == _objTopLevelWriterSemaphore)
                        {
                            await _objReaderSemaphore.WaitAsync(token);
                        }
                    }
                    catch
                    {
                        Interlocked.Decrement(ref _intCountActiveReaders);
                        throw;
                    }
                }
            }
            catch (OperationCanceledException)
            {
                //swallow this because it must be handled as a disposal in the original ExecutionContext
            }
            return(objRelease);
        }
        private async Task <IAsyncDisposable> TakeWriteLockCoreAsync(DebuggableSemaphoreSlim objCurrentSemaphore, SafeWriterSemaphoreRelease objRelease)
        {
            await objCurrentSemaphore.WaitAsync();

            if (Interlocked.Increment(ref _intCountActiveReaders) == 1)
            {
                try
                {
                    // Wait for the reader lock only if there have been no other write locks before us
                    if (_objTopLevelWriterSemaphore.CurrentCount != 0 || objCurrentSemaphore == _objTopLevelWriterSemaphore)
                    {
                        await _objReaderSemaphore.WaitAsync();
                    }
                }
                catch
                {
                    Interlocked.Decrement(ref _intCountActiveReaders);
                    throw;
                }
            }
            return(objRelease);
        }