//**************************************** private void Cancel(KeyedLockInstance instance) { // Retrieve the current lock state while (_Locks.TryGetValue(instance.Key !, out var OldQueue)) { // Can get no results (or no queue at all) if // - someone releases while we cancel and Release removes us from the queue // - we disposed if (OldQueue.IsEmpty) { return; } var NewQueue = OldQueue.Remove(instance); if (ReferenceEquals(OldQueue, NewQueue)) { return; } // Apply the changes to the lock queue if (_Locks.TryUpdate(instance.Key !, NewQueue, OldQueue)) { return; } // Someone modified the lock queue, probably a new waiter, so retry } }
/// <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); }
//**************************************** internal Instance(KeyedLockInstance instance) { _Instance = instance; _Token = instance.Version; }