private void Release() { WaiterInfo?info = null; lock (this.syncObject) { if (this.CurrentCount++ == 0) { // We loop because the First node may have been canceled. while (this.waiters.First is { } head) { // Remove the head of the queue. this.waiters.RemoveFirst(); info = head.Value; this.RecycleNode(head); if (info.Trigger.TrySetResult(new Releaser(this))) { // We successfully let someone enter the semaphore. this.CurrentCount--; // We've filled the one slot available in the semaphore. Stop looking for more. break; } } } } // Release memory related to cancellation handling. info?.Cleanup(); }
/// <summary> /// Requests access to the lock. /// </summary> /// <param name="timeout">A timeout for waiting for the lock.</param> /// <param name="cancellationToken">A token whose cancellation signals lost interest in the lock.</param> /// <returns> /// A task whose result is a releaser that should be disposed to release the lock. /// This task may be canceled if <paramref name="cancellationToken"/> is signaled or <paramref name="timeout"/> expires. /// </returns> /// <exception cref="OperationCanceledException">Thrown when <paramref name="cancellationToken"/> is canceled or the <paramref name="timeout"/> expires before semaphore access is granted.</exception> /// <exception cref="ObjectDisposedException">Thrown when this semaphore is disposed before semaphore access is granted.</exception> public Task <Releaser> EnterAsync(TimeSpan timeout, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { return(Task.FromCanceled <Releaser>(cancellationToken)); } lock (this.syncObject) { if (this.disposed) { return(DisposedReleaserTask); } if (this.CurrentCount > 0) { this.CurrentCount--; return(this.uncontestedReleaser); } else if (timeout == TimeSpan.Zero) { return(CanceledReleaser); } else { WaiterInfo info = new WaiterInfo(this, cancellationToken); LinkedListNode <WaiterInfo>?node = this.GetNode(info); // Careful: consider that if the token was cancelled just now (after we checked it on entry to this method) // or the timeout expires, // then this Register method may *inline* the handler we give it, reversing the apparent order of execution with respect to // the code that follows this Register call. info.CancellationTokenRegistration = cancellationToken.Register(s => CancellationHandler(s), info); if (timeout != Timeout.InfiniteTimeSpan) { info.TimerTokenSource = new Timer(s => CancellationHandler(s), info, checked ((int)timeout.TotalMilliseconds), Timeout.Infinite); } // Only add to the queue if cancellation hasn't already happened. if (!info.Trigger.Task.IsCanceled) { this.waiters.AddLast(node); info.Node = node; } else { // Make sure we don't leak the Timer if cancellation happened before we created it. info.Cleanup(); // Also recycle the unused node. this.RecycleNode(node); } return(info.Trigger.Task); } } }