You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Zero/ZeroLevel/Services/Async/AsyncReaderWriterLock.cs

770 lines
31 KiB

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace ZeroLevel.Services.Async
{
/// <summary>
/// A reader/writer lock that is compatible with async. Note that this lock is <b>not</b> recursive!
/// </summary>
[DebuggerDisplay("Id = {Id}, State = {GetStateForDebugger}, ReaderCount = {GetReaderCountForDebugger}, UpgradeInProgress = {GetUpgradeInProgressForDebugger}")]
[DebuggerTypeProxy(typeof(DebugView))]
public sealed class AsyncReaderWriterLock
{
/// <summary>
/// The queue of TCSs that other tasks are awaiting to acquire the lock as writers.
/// </summary>
private readonly IAsyncWaitQueue<IDisposable> _writerQueue;
/// <summary>
/// The queue of TCSs that other tasks are awaiting to acquire the lock as readers.
/// </summary>
private readonly IAsyncWaitQueue<IDisposable> _readerQueue;
/// <summary>
/// The queue of TCSs that other tasks are awaiting to acquire the lock as upgradeable readers.
/// </summary>
private readonly IAsyncWaitQueue<UpgradeableReaderKey> _upgradeableReaderQueue;
/// <summary>
/// The queue of TCSs that other tasks are awaiting to upgrade a reader lock to a writer lock.
/// </summary>
private readonly IAsyncWaitQueue<IDisposable> _upgradeReaderQueue;
/// <summary>
/// The semi-unique identifier for this instance. This is 0 if the id has not yet been created.
/// </summary>
private int _id;
/// <summary>
/// The current upgradeable reader lock key, if any. If this is not <c>null</c>, then there is an upgradeable reader lock held.
/// </summary>
private UpgradeableReaderKey _upgradeableReaderKey;
/// <summary>
/// Number of reader locks held (including an upgradeable reader lock, if applicable); -1 if a writer lock is held; 0 if no locks are held.
/// </summary>
private int _locksHeld;
/// <summary>
/// The object used for mutual exclusion.
/// </summary>
private readonly object _mutex;
[DebuggerNonUserCode]
internal State GetStateForDebugger
{
get
{
if (_locksHeld == 0)
return State.Unlocked;
if (_locksHeld == -1)
if (_upgradeableReaderKey != null)
return State.WriteLockedWithUpgradeableReader;
else
return State.WriteLocked;
if (_upgradeableReaderKey != null)
return State.ReadLockedWithUpgradeableReader;
return State.ReadLocked;
}
}
internal enum State
{
Unlocked,
ReadLocked,
ReadLockedWithUpgradeableReader,
WriteLocked,
WriteLockedWithUpgradeableReader,
}
[DebuggerNonUserCode]
internal int GetReaderCountForDebugger { get { return (_locksHeld > 0 ? _locksHeld : 0); } }
[DebuggerNonUserCode]
internal bool GetUpgradeInProgressForDebugger { get { return !_upgradeReaderQueue.IsEmpty; } }
/// <summary>
/// Creates a new async-compatible reader/writer lock.
/// </summary>
public AsyncReaderWriterLock(IAsyncWaitQueue<IDisposable> writerQueue, IAsyncWaitQueue<IDisposable> readerQueue,
IAsyncWaitQueue<UpgradeableReaderKey> upgradeableReaderQueue, IAsyncWaitQueue<IDisposable> upgradeReaderQueue)
{
_writerQueue = writerQueue;
_readerQueue = readerQueue;
_upgradeableReaderQueue = upgradeableReaderQueue;
_upgradeReaderQueue = upgradeReaderQueue;
_mutex = new object();
}
/// <summary>
/// Creates a new async-compatible reader/writer lock.
/// </summary>
public AsyncReaderWriterLock()
: this(new DefaultAsyncWaitQueue<IDisposable>(), new DefaultAsyncWaitQueue<IDisposable>(),
new DefaultAsyncWaitQueue<UpgradeableReaderKey>(), new DefaultAsyncWaitQueue<IDisposable>())
{
}
/// <summary>
/// Gets a semi-unique identifier for this asynchronous lock.
/// </summary>
public int Id
{
get { return IdManager<AsyncReaderWriterLock>.GetId(ref _id); }
}
internal object SyncObject
{
get { return _mutex; }
}
/// <summary>
/// Applies a continuation to the task that will call <see cref="ReleaseWaiters"/> if the task is canceled. This method may not be called while holding the sync lock.
/// </summary>
/// <param name="task">The task to observe for cancellation.</param>
private void ReleaseWaitersWhenCanceled(Task task)
{
task.ContinueWith(t =>
{
List<IDisposable> finishes;
lock (SyncObject) { finishes = ReleaseWaiters(); }
foreach (var finish in finishes)
finish.Dispose();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
/// <summary>
/// Asynchronously acquires the lock as a reader. Returns a disposable that releases the lock when disposed.
/// </summary>
/// <param name="cancellationToken">The cancellation token used to cancel the lock. If this is already set, then this method will attempt to take the lock immediately (succeeding if the lock is currently available).</param>
/// <returns>A disposable that releases the lock when disposed.</returns>
public Task<IDisposable> ReaderLockAsync(CancellationToken cancellationToken)
{
Task<IDisposable> ret;
lock (SyncObject)
{
// If the lock is available or in read mode and there are no waiting writers, upgradeable readers, or upgrading readers, take it immediately.
if (_locksHeld >= 0 && _writerQueue.IsEmpty && _upgradeableReaderQueue.IsEmpty && _upgradeReaderQueue.IsEmpty)
{
++_locksHeld;
ret = TaskShim.FromResult<IDisposable>(new ReaderKey(this));
}
else
{
// Wait for the lock to become available or cancellation.
ret = _readerQueue.Enqueue(cancellationToken);
}
}
return ret;
}
/// <summary>
/// Synchronously acquires the lock as a reader. Returns a disposable that releases the lock when disposed. This method may block the calling thread.
/// </summary>
/// <param name="cancellationToken">The cancellation token used to cancel the lock. If this is already set, then this method will attempt to take the lock immediately (succeeding if the lock is currently available).</param>
/// <returns>A disposable that releases the lock when disposed.</returns>
public IDisposable ReaderLock(CancellationToken cancellationToken)
{
Task<IDisposable> ret;
lock (SyncObject)
{
// If the lock is available or in read mode and there are no waiting writers, upgradeable readers, or upgrading readers, take it immediately.
if (_locksHeld >= 0 && _writerQueue.IsEmpty && _upgradeableReaderQueue.IsEmpty && _upgradeReaderQueue.IsEmpty)
{
++_locksHeld;
return new ReaderKey(this);
}
// Wait for the lock to become available or cancellation.
ret = _readerQueue.Enqueue(cancellationToken);
}
return ret.WaitAndUnwrapException();
}
/// <summary>
/// Asynchronously acquires the lock as a reader. Returns a disposable that releases the lock when disposed.
/// </summary>
/// <returns>A disposable that releases the lock when disposed.</returns>
public Task<IDisposable> ReaderLockAsync()
{
return ReaderLockAsync(CancellationToken.None);
}
/// <summary>
/// Synchronously acquires the lock as a reader. Returns a disposable that releases the lock when disposed. This method may block the calling thread.
/// </summary>
/// <returns>A disposable that releases the lock when disposed.</returns>
public IDisposable ReaderLock()
{
return ReaderLock(CancellationToken.None);
}
/// <summary>
/// Asynchronously acquires the lock as a writer. Returns a disposable that releases the lock when disposed.
/// </summary>
/// <param name="cancellationToken">The cancellation token used to cancel the lock. If this is already set, then this method will attempt to take the lock immediately (succeeding if the lock is currently available).</param>
/// <returns>A disposable that releases the lock when disposed.</returns>
public Task<IDisposable> WriterLockAsync(CancellationToken cancellationToken)
{
Task<IDisposable> ret;
lock (SyncObject)
{
// If the lock is available, take it immediately.
if (_locksHeld == 0)
{
_locksHeld = -1;
ret = TaskShim.FromResult<IDisposable>(new WriterKey(this));
}
else
{
// Wait for the lock to become available or cancellation.
ret = _writerQueue.Enqueue(cancellationToken);
}
}
ReleaseWaitersWhenCanceled(ret);
return ret;
}
/// <summary>
/// Synchronously acquires the lock as a writer. Returns a disposable that releases the lock when disposed. This method may block the calling thread.
/// </summary>
/// <param name="cancellationToken">The cancellation token used to cancel the lock. If this is already set, then this method will attempt to take the lock immediately (succeeding if the lock is currently available).</param>
/// <returns>A disposable that releases the lock when disposed.</returns>
public IDisposable WriterLock(CancellationToken cancellationToken)
{
Task<IDisposable> ret;
lock (SyncObject)
{
// If the lock is available, take it immediately.
if (_locksHeld == 0)
{
_locksHeld = -1;
return new WriterKey(this);
}
// Wait for the lock to become available or cancellation.
ret = _writerQueue.Enqueue(cancellationToken);
}
ReleaseWaitersWhenCanceled(ret);
return ret.WaitAndUnwrapException();
}
/// <summary>
/// Asynchronously acquires the lock as a writer. Returns a disposable that releases the lock when disposed.
/// </summary>
/// <returns>A disposable that releases the lock when disposed.</returns>
public Task<IDisposable> WriterLockAsync()
{
return WriterLockAsync(CancellationToken.None);
}
/// <summary>
/// Asynchronously acquires the lock as a writer. Returns a disposable that releases the lock when disposed. This method may block the calling thread.
/// </summary>
/// <returns>A disposable that releases the lock when disposed.</returns>
public IDisposable WriterLock()
{
return WriterLock(CancellationToken.None);
}
/// <summary>
/// Asynchronously acquires the lock as a reader with the option to upgrade. Returns a key that can be used to upgrade and downgrade the lock, and releases the lock when disposed.
/// </summary>
/// <param name="cancellationToken">The cancellation token used to cancel the lock. If this is already set, then this method will attempt to take the lock immediately (succeeding if the lock is currently available).</param>
/// <returns>A key that can be used to upgrade and downgrade this lock, and releases the lock when disposed.</returns>
public Task<UpgradeableReaderKey> UpgradeableReaderLockAsync(CancellationToken cancellationToken)
{
Task<UpgradeableReaderKey> ret;
lock (SyncObject)
{
// If the lock is available, take it immediately.
if (_locksHeld == 0 || (_locksHeld > 0 && _upgradeableReaderKey == null))
{
++_locksHeld;
_upgradeableReaderKey = new UpgradeableReaderKey(this);
ret = TaskShim.FromResult(_upgradeableReaderKey);
}
else
{
// Wait for the lock to become available or cancellation.
ret = _upgradeableReaderQueue.Enqueue(cancellationToken);
}
}
ReleaseWaitersWhenCanceled(ret);
return ret;
}
/// <summary>
/// Synchronously acquires the lock as a reader with the option to upgrade. Returns a key that can be used to upgrade and downgrade the lock, and releases the lock when disposed. This method may block the calling thread.
/// </summary>
/// <param name="cancellationToken">The cancellation token used to cancel the lock. If this is already set, then this method will attempt to take the lock immediately (succeeding if the lock is currently available).</param>
/// <returns>A key that can be used to upgrade and downgrade this lock, and releases the lock when disposed.</returns>
public UpgradeableReaderKey UpgradeableReaderLock(CancellationToken cancellationToken)
{
Task<UpgradeableReaderKey> ret;
lock (SyncObject)
{
// If the lock is available, take it immediately.
if (_locksHeld == 0 || (_locksHeld > 0 && _upgradeableReaderKey == null))
{
++_locksHeld;
_upgradeableReaderKey = new UpgradeableReaderKey(this);
return _upgradeableReaderKey;
}
// Wait for the lock to become available or cancellation.
ret = _upgradeableReaderQueue.Enqueue(cancellationToken);
}
ReleaseWaitersWhenCanceled(ret);
return ret.WaitAndUnwrapException();
}
/// <summary>
/// Asynchronously acquires the lock as a reader with the option to upgrade. Returns a key that can be used to upgrade and downgrade the lock, and releases the lock when disposed.
/// </summary>
/// <returns>A key that can be used to upgrade and downgrade this lock, and releases the lock when disposed.</returns>
public Task<UpgradeableReaderKey> UpgradeableReaderLockAsync()
{
return UpgradeableReaderLockAsync(CancellationToken.None);
}
/// <summary>
/// Synchronously acquires the lock as a reader with the option to upgrade. Returns a key that can be used to upgrade and downgrade the lock, and releases the lock when disposed. This method may block the calling thread.
/// </summary>
/// <returns>A key that can be used to upgrade and downgrade this lock, and releases the lock when disposed.</returns>
public UpgradeableReaderKey UpgradeableReaderLock()
{
return UpgradeableReaderLock(CancellationToken.None);
}
/// <summary>
/// Asynchronously upgrades a reader lock to a writer lock. This method assumes the sync lock is already held.
/// </summary>
internal Task<IDisposable> UpgradeAsync(CancellationToken cancellationToken)
{
Task<IDisposable> ret;
// If the lock is available, take it immediately.
if (_locksHeld == 1)
{
_locksHeld = -1;
ret = TaskShim.FromResult<IDisposable>(new UpgradeableReaderKey.UpgradeKey(_upgradeableReaderKey));
}
else
{
// Wait for the lock to become available or cancellation.
ret = _upgradeReaderQueue.Enqueue(cancellationToken);
}
return ret;
}
/// <summary>
/// Downgrades a writer lock to a reader lock. This method assumes the sync lock is already held.
/// </summary>
internal List<IDisposable> Downgrade()
{
_locksHeld = 1;
return ReleaseWaiters();
}
/// <summary>
/// Grants lock(s) to waiting tasks. This method assumes the sync lock is already held.
/// </summary>
private List<IDisposable> ReleaseWaiters()
{
var ret = new List<IDisposable>();
if (_locksHeld == 0)
{
// Give priority to writers.
if (!_writerQueue.IsEmpty)
{
ret.Add(_writerQueue.Dequeue(new WriterKey(this)));
_locksHeld = -1;
return ret;
}
// Then to upgradeable readers.
if (!_upgradeableReaderQueue.IsEmpty)
{
_upgradeableReaderKey = new UpgradeableReaderKey(this);
ret.Add(_upgradeableReaderQueue.Dequeue(_upgradeableReaderKey));
++_locksHeld;
}
// Finally to readers.
while (!_readerQueue.IsEmpty)
{
ret.Add(_readerQueue.Dequeue(new ReaderKey(this)));
++_locksHeld;
}
return ret;
}
// Give priority to upgrading readers.
if (_locksHeld == 1)
{
if (!_upgradeReaderQueue.IsEmpty)
{
ret.Add(_upgradeReaderQueue.Dequeue(new UpgradeableReaderKey.UpgradeKey(_upgradeableReaderKey)));
_locksHeld = -1;
}
}
if (_locksHeld > 0)
{
// If there are current reader locks and waiting writers, then do nothing.
if (!_writerQueue.IsEmpty || !_upgradeableReaderQueue.IsEmpty || !_upgradeReaderQueue.IsEmpty)
return ret;
// If there are current reader locks but no upgradeable reader lock, try to release an upgradeable reader.
if (_upgradeableReaderKey == null && !_upgradeableReaderQueue.IsEmpty)
{
_upgradeableReaderKey = new UpgradeableReaderKey(this);
ret.Add(_upgradeableReaderQueue.Dequeue(_upgradeableReaderKey));
}
}
return ret;
}
/// <summary>
/// Releases the lock as a reader.
/// </summary>
internal void ReleaseReaderLock()
{
List<IDisposable> finishes;
lock (SyncObject)
{
--_locksHeld;
finishes = ReleaseWaiters();
}
foreach (var finish in finishes)
finish.Dispose();
}
/// <summary>
/// Releases the lock as a writer.
/// </summary>
internal void ReleaseWriterLock()
{
List<IDisposable> finishes;
lock (SyncObject)
{
_locksHeld = 0;
finishes = ReleaseWaiters();
}
foreach (var finish in finishes)
finish.Dispose();
}
/// <summary>
/// Releases the lock as an upgradeable reader.
/// </summary>
internal void ReleaseUpgradeableReaderLock(Task upgrade)
{
IDisposable cancelFinish = null;
List<IDisposable> finishes;
lock (SyncObject)
{
if (upgrade != null)
cancelFinish = _upgradeReaderQueue.TryCancel(upgrade);
_upgradeableReaderKey = null;
--_locksHeld;
finishes = ReleaseWaiters();
}
if (cancelFinish != null)
cancelFinish.Dispose();
foreach (var finish in finishes)
finish.Dispose();
}
/// <summary>
/// The disposable which releases the reader lock.
/// </summary>
private sealed class ReaderKey : IDisposable
{
/// <summary>
/// The lock to release.
/// </summary>
private AsyncReaderWriterLock _asyncReaderWriterLock;
/// <summary>
/// Creates the key for a lock.
/// </summary>
/// <param name="asyncReaderWriterLock">The lock to release. May not be <c>null</c>.</param>
public ReaderKey(AsyncReaderWriterLock asyncReaderWriterLock)
{
_asyncReaderWriterLock = asyncReaderWriterLock;
}
/// <summary>
/// Release the lock.
/// </summary>
public void Dispose()
{
if (_asyncReaderWriterLock == null)
return;
_asyncReaderWriterLock.ReleaseReaderLock();
_asyncReaderWriterLock = null;
}
}
/// <summary>
/// The disposable which releases the writer lock.
/// </summary>
private sealed class WriterKey : IDisposable
{
/// <summary>
/// The lock to release.
/// </summary>
private AsyncReaderWriterLock _asyncReaderWriterLock;
/// <summary>
/// Creates the key for a lock.
/// </summary>
/// <param name="asyncReaderWriterLock">The lock to release. May not be <c>null</c>.</param>
public WriterKey(AsyncReaderWriterLock asyncReaderWriterLock)
{
_asyncReaderWriterLock = asyncReaderWriterLock;
}
/// <summary>
/// Release the lock.
/// </summary>
public void Dispose()
{
if (_asyncReaderWriterLock == null)
return;
_asyncReaderWriterLock.ReleaseWriterLock();
_asyncReaderWriterLock = null;
}
}
/// <summary>
/// The disposable which manages the upgradeable reader lock.
/// </summary>
[DebuggerDisplay("State = {GetStateForDebugger}, ReaderWriterLockId = {_asyncReaderWriterLock.Id}")]
public sealed class UpgradeableReaderKey : IDisposable
{
/// <summary>
/// The lock to release.
/// </summary>
private readonly AsyncReaderWriterLock _asyncReaderWriterLock;
/// <summary>
/// The task doing the upgrade.
/// </summary>
private Task<IDisposable> _upgrade;
/// <summary>
/// Whether or not this instance has been disposed.
/// </summary>
private bool _disposed;
[DebuggerNonUserCode]
internal State GetStateForDebugger
{
get
{
if (_upgrade == null)
return State.Reader;
if (_upgrade.Status == TaskStatus.RanToCompletion)
return State.Writer;
return State.UpgradingToWriter;
}
}
internal enum State
{
Reader,
UpgradingToWriter,
Writer,
}
/// <summary>
/// Creates the key for a lock.
/// </summary>
/// <param name="asyncReaderWriterLock">The lock to release. May not be <c>null</c>.</param>
internal UpgradeableReaderKey(AsyncReaderWriterLock asyncReaderWriterLock)
{
_asyncReaderWriterLock = asyncReaderWriterLock;
}
/// <summary>
/// Gets a value indicating whether this lock has been upgraded to a write lock.
/// </summary>
public bool Upgraded
{
get
{
Task task;
lock (_asyncReaderWriterLock.SyncObject) { task = _upgrade; }
return (task != null && task.Status == TaskStatus.RanToCompletion);
}
}
/// <summary>
/// Upgrades the reader lock to a writer lock. Returns a disposable that downgrades the writer lock to a reader lock when disposed.
/// </summary>
/// <param name="cancellationToken">The cancellation token used to cancel the upgrade. If this is already set, then this method will attempt to upgrade immediately (succeeding if the lock is currently available).</param>
public Task<IDisposable> UpgradeAsync(CancellationToken cancellationToken)
{
lock (_asyncReaderWriterLock.SyncObject)
{
if (_upgrade != null)
throw new InvalidOperationException("Cannot upgrade.");
_upgrade = _asyncReaderWriterLock.UpgradeAsync(cancellationToken);
}
_asyncReaderWriterLock.ReleaseWaitersWhenCanceled(_upgrade);
var ret = new TaskCompletionSource<IDisposable>();
_upgrade.ContinueWith(t =>
{
if (t.IsCanceled)
lock (_asyncReaderWriterLock.SyncObject) { _upgrade = null; }
ret.TryCompleteFromCompletedTask(t);
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
return ret.Task;
}
/// <summary>
/// Synchronously upgrades the reader lock to a writer lock. Returns a disposable that downgrades the writer lock to a reader lock when disposed. This method may block the calling thread.
/// </summary>
/// <param name="cancellationToken">The cancellation token used to cancel the upgrade. If this is already set, then this method will attempt to upgrade immediately (succeeding if the lock is currently available).</param>
public IDisposable Upgrade(CancellationToken cancellationToken)
{
lock (_asyncReaderWriterLock.SyncObject)
{
if (_upgrade != null)
throw new InvalidOperationException("Cannot upgrade.");
_upgrade = _asyncReaderWriterLock.UpgradeAsync(cancellationToken);
}
_asyncReaderWriterLock.ReleaseWaitersWhenCanceled(_upgrade);
var ret = new TaskCompletionSource<IDisposable>();
_upgrade.ContinueWith(t =>
{
if (t.IsCanceled)
lock (_asyncReaderWriterLock.SyncObject) { _upgrade = null; }
ret.TryCompleteFromCompletedTask(t);
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
return ret.Task.WaitAndUnwrapException();
}
/// <summary>
/// Upgrades the reader lock to a writer lock. Returns a disposable that downgrades the writer lock to a reader lock when disposed.
/// </summary>
public Task<IDisposable> UpgradeAsync()
{
return UpgradeAsync(CancellationToken.None);
}
/// <summary>
/// Synchronously upgrades the reader lock to a writer lock. Returns a disposable that downgrades the writer lock to a reader lock when disposed. This method may block the calling thread.
/// </summary>
public IDisposable Upgrade()
{
return Upgrade(CancellationToken.None);
}
/// <summary>
/// Downgrades the writer lock to a reader lock.
/// </summary>
private void Downgrade()
{
List<IDisposable> finishes;
lock (_asyncReaderWriterLock.SyncObject)
{
finishes = _asyncReaderWriterLock.Downgrade();
_upgrade = null;
}
foreach (var finish in finishes)
finish.Dispose();
}
/// <summary>
/// Release the lock.
/// </summary>
public void Dispose()
{
if (_disposed)
return;
_asyncReaderWriterLock.ReleaseUpgradeableReaderLock(_upgrade);
_disposed = true;
}
/// <summary>
/// The disposable which downgrades an upgradeable reader key.
/// </summary>
internal sealed class UpgradeKey : IDisposable
{
/// <summary>
/// The upgradeable reader key to downgrade.
/// </summary>
private UpgradeableReaderKey _key;
/// <summary>
/// Creates the upgrade key for an upgradeable reader key.
/// </summary>
/// <param name="key">The upgradeable reader key to downgrade. May not be <c>null</c>.</param>
public UpgradeKey(UpgradeableReaderKey key)
{
_key = key;
}
/// <summary>
/// Downgrade the upgradeable reader key.
/// </summary>
public void Dispose()
{
if (_key == null)
return;
_key.Downgrade();
_key = null;
}
}
}
// ReSharper disable UnusedMember.Local
[DebuggerNonUserCode]
private sealed class DebugView
{
private readonly AsyncReaderWriterLock _rwl;
public DebugView(AsyncReaderWriterLock rwl)
{
_rwl = rwl;
}
public int Id { get { return _rwl.Id; } }
public State State { get { return _rwl.GetStateForDebugger; } }
public int ReaderCount { get { return _rwl.GetReaderCountForDebugger; } }
public bool UpgradeInProgress { get { return _rwl.GetUpgradeInProgressForDebugger; } }
public IAsyncWaitQueue<IDisposable> ReaderWaitQueue { get { return _rwl._readerQueue; } }
public IAsyncWaitQueue<IDisposable> WriterWaitQueue { get { return _rwl._writerQueue; } }
public IAsyncWaitQueue<UpgradeableReaderKey> UpgradeableReaderWaitQueue { get { return _rwl._upgradeableReaderQueue; } }
public IAsyncWaitQueue<IDisposable> UpgradeReaderWaitQueue { get { return _rwl._upgradeReaderQueue; } }
}
// ReSharper restore UnusedMember.Local
}
}

Powered by TurnKey Linux.