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); }