Beispiel #1
0
        /// <summary>
        /// Attempts to take a lock
        /// </summary>
        /// <param name="key">The key to lock on</param>
        /// <param name="timeout">The amount of time to wait for the lock</param>
        /// <param name="token">A cancellation token that can be used to abort waiting on the lock</param>
        /// <returns>A task that completes when the lock is taken, giving an IDisposable to release the lock</returns>
        /// <exception cref="OperationCanceledException">The given cancellation token was cancelled</exception>
        /// <exception cref="TimeoutException">The timeout elapsed</exception>
        public ValueTask <Instance> Lock(TKey key, TimeSpan timeout, CancellationToken token = default)
        {         //****************************************
            ImmutableList <KeyedLockInstance> NewValue;
            var AppliedCancellation = false;

            //****************************************

            if (key == null)
            {
                throw new ArgumentNullException(nameof(key), "Key cannot be null");
            }

            // Is this keyed lock disposing?
            if (_Disposer != null)
            {
                throw new ObjectDisposedException(nameof(AsyncKeyedLock <TKey>), "Keyed Lock has been disposed of");
            }

            var NewInstance = KeyedLockInstance.GetOrCreate(this, key);

            // Try and take the lock, or add ourselves to the lock queue
            for (; ;)
            {
                if (_Locks.TryGetValue(key, out var OldValue))
                {
                    if (AppliedCancellation)
                    {
                        if (!NewInstance.IsPending)
                        {
                            // Cancelled while trying to queue
                            return(new ValueTask <Instance>(NewInstance, NewInstance.Version));
                        }
                    }
                    else
                    {
                        // We must have cancellation applied before adding to the queue
                        NewInstance.ApplyCancellation(token, timeout);

                        AppliedCancellation = true;
                    }

                    // Add ourselves to the lock queue
                    NewValue = OldValue.Add(NewInstance);

                    if (_Locks.TryUpdate(key, NewValue, OldValue))
                    {
                        break;
                    }
                }
                else
                {
                    // An empty queue means we hold the lock
                    if (_Locks.TryAdd(key, NewValue = ImmutableList <KeyedLockInstance> .Empty))
                    {
                        break;
                    }
                }
            }

            var ValueTask = new ValueTask <Instance>(NewInstance, NewInstance.Version);

            // Did we dispose along the way
            if (_Disposer == null)
            {
                // If the queue is empty then we hold the lock
                if (NewValue.IsEmpty)
                {
                    // If we cancelled between taking the lock and now, we need to release it so someone else can possibly take it
                    if (!NewInstance.TrySwitchToCompleted())
                    {
                        Release(key);
                    }
                }
            }
            else
            {
                if (NewValue.IsEmpty)
                {
                    // If we didn't add ourselves to the queue, we need to dispose ourselves
                    NewInstance.SwitchToDisposed();

                    // Now release the lock we took, to trigger disposal
                    Release(key);
                }
                else
                {
                    // Dispose of the waiter (if the existing lock doesn't release and do it for us)
                    DisposeWaiters();
                }
            }

            return(ValueTask);
        }