/// <summary>
        /// Can be disposed from any thread
        /// </summary>
        /// <returns></returns>
        public async Task <IDisposable> LockWriteAsync(int millisecondsTimeout = int.MaxValue)
        {
            MyLockQueueItem queueItem;
            IDisposable     answer;

            lock (_lock)
            {
                // If there's no items
                if (_queue.Count == 0)
                {
                    // Create a new write entry
                    var newEntry = new MyWriteLockQueueItem(this, true);

                    // Add it to the queue
                    _queue.Add(newEntry);

                    // We're already locked, so just return instantly
                    return(new MyDisposableLock(newEntry));
                }

                // Otherwise, we always just add for write locks
                else
                {
                    var newEntry = new MyWriteLockQueueItem(this, alreadyLocked: false);

                    _queue.Add(newEntry);

                    queueItem = newEntry;
                    answer    = new MyDisposableLock(newEntry);
                }
            }

            try
            {
                await TimeoutTask.Create(queueItem.LockedTask, millisecondsTimeout);
            }
            catch (TimeoutException)
            {
                queueItem.Release();
                throw new TimeoutException("Timeout exceeded and lock not established.");
            }
            return(answer);
        }
        /// <summary>
        /// Can be disposed from any thread
        /// </summary>
        /// <returns></returns>
        public async Task <IDisposable> LockReadAsync(int millisecondsTimeout = int.MaxValue)
        {
            MyLockQueueItem queueItem;
            IDisposable     answer;

            lock (_lock)
            {
                // If there's no items
                if (_queue.Count == 0)
                {
                    // Create a new read entry
                    var newEntry = new MyReadLockQueueItem(this, true);

                    // Add it to the queue
                    _queue.Add(newEntry);

                    // We're already locked, so just return instantly
                    return(new MyDisposableLock(newEntry));
                }

                // If there's only one item, and it's another read, merge with it
                if (_queue.Count == 1 && _queue[0] is MyReadLockQueueItem)
                {
                    (_queue[0] as MyReadLockQueueItem).Add();

                    // We're already locked, so just return instantly
                    return(new MyDisposableLock(_queue[0]));
                }

                // If there's a read at the end, merge with it
                else if (_queue.Last() is MyReadLockQueueItem)
                {
                    var last = _queue.Last() as MyReadLockQueueItem;
                    last.Add();

                    // Need to wait till this one starts
                    queueItem = last;
                    answer    = new MyDisposableLock(last);
                }

                // Otherwise, the last item is a write, so we need to add new and wait
                else
                {
                    var newEntry = new MyReadLockQueueItem(this, alreadyLocked: false);

                    _queue.Add(newEntry);

                    queueItem = newEntry;
                    answer    = new MyDisposableLock(newEntry);
                }
            }

            try
            {
                await TimeoutTask.Create(queueItem.LockedTask, millisecondsTimeout);
            }
            catch (TimeoutException)
            {
                queueItem.Release();
                throw new TimeoutException("Timeout exceeded and lock not established.");
            }
            return(answer);
        }