public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) { CheckDispose(); // Validate input if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException( nameof(millisecondsTimeout), millisecondsTimeout, SR.SemaphoreSlim_Wait_TimeoutWrong); } cancellationToken.ThrowIfCancellationRequested(); // Perf: Check the stack timeout parameter before checking the volatile count if (millisecondsTimeout == 0 && m_currentCount == 0) { // Pessimistic fail fast, check volatile count outside lock (only when timeout is zero!) return(false); } uint startTime = 0; if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout > 0) { startTime = TimeoutHelper.GetTime(); } bool waitSuccessful = false; Task <bool>?asyncWaitTask = null; bool lockTaken = false; // Register for cancellation outside of the main lock. // NOTE: Register/unregister inside the lock can deadlock as different lock acquisition orders could // occur for (1)this.m_lockObjAndDisposed and (2)cts.internalLock CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.UnsafeRegister(s_cancellationTokenCanceledEventHandler, this); try { // Perf: first spin wait for the count to be positive. // This additional amount of spinwaiting in addition // to Monitor.Enter()'s spinwaiting has shown measurable perf gains in test scenarios. if (m_currentCount == 0) { // Monitor.Enter followed by Monitor.Wait is much more expensive than waiting on an event as it involves another // spin, contention, etc. The usual number of spin iterations that would otherwise be used here is increased to // lessen that extra expense of doing a proper wait. int spinCount = SpinWait.SpinCountforSpinBeforeWait * 4; SpinWait spinner = default; while (spinner.Count < spinCount) { spinner.SpinOnce(sleep1Threshold: -1); if (m_currentCount != 0) { break; } } } Monitor.Enter(m_lockObjAndDisposed, ref lockTaken); m_waitCount++; // If there are any async waiters, for fairness we'll get in line behind // then by translating our synchronous wait into an asynchronous one that we // then block on (once we've released the lock). if (m_asyncHead != null) { Debug.Assert(m_asyncTail != null, "tail should not be null if head isn't"); asyncWaitTask = WaitAsync(millisecondsTimeout, cancellationToken); } // There are no async waiters, so we can proceed with normal synchronous waiting. else { // If the count > 0 we are good to move on. // If not, then wait if we were given allowed some wait duration OperationCanceledException?oce = null; if (m_currentCount == 0) { if (millisecondsTimeout == 0) { return(false); } // Prepare for the main wait... // wait until the count become greater than zero or the timeout is expired try { waitSuccessful = WaitUntilCountOrTimeout(millisecondsTimeout, startTime, cancellationToken); } catch (OperationCanceledException e) { oce = e; } } // Now try to acquire. We prioritize acquisition over cancellation/timeout so that we don't // lose any counts when there are asynchronous waiters in the mix. Asynchronous waiters // defer to synchronous waiters in priority, which means that if it's possible an asynchronous // waiter didn't get released because a synchronous waiter was present, we need to ensure // that synchronous waiter succeeds so that they have a chance to release. Debug.Assert(!waitSuccessful || m_currentCount > 0, "If the wait was successful, there should be count available."); if (m_currentCount > 0) { waitSuccessful = true; m_currentCount--; } else if (oce != null) { throw oce; } // Exposing wait handle which is lazily initialized if needed if (m_waitHandle != null && m_currentCount == 0) { m_waitHandle.Reset(); } } } finally { // Release the lock if (lockTaken) { m_waitCount--; Monitor.Exit(m_lockObjAndDisposed); } // Unregister the cancellation callback. cancellationTokenRegistration.Dispose(); } // If we had to fall back to asynchronous waiting, block on it // here now that we've released the lock, and return its // result when available. Otherwise, this was a synchronous // wait, and whether we successfully acquired the semaphore is // stored in waitSuccessful. return((asyncWaitTask != null) ? asyncWaitTask.GetAwaiter().GetResult() : waitSuccessful); }
public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) { this.ThrowIfDisposed(); cancellationToken.ThrowIfCancellationRequested(); if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout)); } if (!this.IsSet) { if (millisecondsTimeout == 0) { return(false); } uint startTime = 0; bool flag = false; int millisecondsTimeout1 = millisecondsTimeout; if (millisecondsTimeout != -1) { startTime = TimeoutHelper.GetTime(); flag = true; } int spinCount = this.SpinCount; SpinWait spinWait = new SpinWait(); while (spinWait.Count < spinCount) { spinWait.SpinOnce(-1); if (this.IsSet) { return(true); } if (spinWait.Count >= 100 && spinWait.Count % 10 == 0) { cancellationToken.ThrowIfCancellationRequested(); } } this.EnsureLockObjectCreated(); using (cancellationToken.UnsafeRegister(ManualResetEventSlim.s_cancellationTokenCallback, (object)this)) { lock (this.m_lock) { while (!this.IsSet) { cancellationToken.ThrowIfCancellationRequested(); if (flag) { millisecondsTimeout1 = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout); if (millisecondsTimeout1 <= 0) { return(false); } } ++this.Waiters; if (this.IsSet) { --this.Waiters; return(true); } try { if (!Monitor.Wait(this.m_lock, millisecondsTimeout1)) { return(false); } } finally { --this.Waiters; } } } } } return(true); }
public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) { ThrowIfDisposed(); cancellationToken.ThrowIfCancellationRequested(); // an early convenience check if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout)); } if (!IsSet) { if (millisecondsTimeout == 0) { // For 0-timeouts, we just return immediately. return(false); } // We spin briefly before falling back to allocating and/or waiting on a true event. uint startTime = 0; bool bNeedTimeoutAdjustment = false; int realMillisecondsTimeout = millisecondsTimeout; //this will be adjusted if necessary. if (millisecondsTimeout != Timeout.Infinite) { // We will account for time spent spinning, so that we can decrement it from our // timeout. In most cases the time spent in this section will be negligible. But // we can't discount the possibility of our thread being switched out for a lengthy // period of time. The timeout adjustments only take effect when and if we actually // decide to block in the kernel below. startTime = TimeoutHelper.GetTime(); bNeedTimeoutAdjustment = true; } // Spin int spinCount = SpinCount; var spinner = new SpinWait(); while (spinner.Count < spinCount) { spinner.SpinOnce(sleep1Threshold: -1); if (IsSet) { return(true); } if (spinner.Count >= 100 && spinner.Count % 10 == 0) // check the cancellation token if the user passed a very large spin count { cancellationToken.ThrowIfCancellationRequested(); } } // Now enter the lock and wait. Must be created before registering the cancellation callback, // which will try to take this lock. EnsureLockObjectCreated(); // We must register and unregister the token outside of the lock, to avoid deadlocks. using (cancellationToken.UnsafeRegister(s_cancellationTokenCallback, this)) { lock (m_lock !) { // Loop to cope with spurious wakeups from other waits being canceled while (!IsSet) { // If our token was canceled, we must throw and exit. cancellationToken.ThrowIfCancellationRequested(); //update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled) if (bNeedTimeoutAdjustment) { realMillisecondsTimeout = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout); if (realMillisecondsTimeout <= 0) { return(false); } } // There is a race condition that Set will fail to see that there are waiters as Set does not take the lock, // so after updating waiters, we must check IsSet again. // Also, we must ensure there cannot be any reordering of the assignment to Waiters and the // read from IsSet. This is guaranteed as Waiters{set;} involves an Interlocked.CompareExchange // operation which provides a full memory barrier. // If we see IsSet=false, then we are guaranteed that Set() will see that we are // waiting and will pulse the monitor correctly. Waiters = Waiters + 1; if (IsSet) //This check must occur after updating Waiters. { Waiters--; //revert the increment. return(true); } // Now finally perform the wait. try { // ** the actual wait ** if (!Monitor.Wait(m_lock, realMillisecondsTimeout)) { return(false); //return immediately if the timeout has expired. } } finally { // Clean up: we're done waiting. Waiters = Waiters - 1; } // Now just loop back around, and the right thing will happen. Either: // 1. We had a spurious wake-up due to some other wait being canceled via a different cancellationToken (rewait) // or 2. the wait was successful. (the loop will break) } } } } // automatically disposes (and unregisters) the callback return(true); //done. The wait was satisfied. }
/// <summary>Asynchronously waits to enter the mutex.</summary> /// <param name="cancellationToken">The CancellationToken token to observe.</param> /// <returns>A task that will complete when the mutex has been entered or the enter canceled.</returns> public Task EnterAsync(CancellationToken cancellationToken) { // If cancellation was requested, bail immediately. // If the mutex is not currently held nor contended, enter immediately. // Otherwise, fall back to a more expensive likely-asynchronous wait. return (cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Interlocked.Decrement(ref _gate) >= 0 ? Task.CompletedTask : Contended(cancellationToken)); // Everything that follows is the equivalent of: // return _sem.WaitAsync(cancellationToken); // if _sem were to be constructed as `new SemaphoreSlim(0)`. Task Contended(CancellationToken cancellationToken) { var w = new Waiter(this); // We need to register for cancellation before storing the waiter into the list. // If we registered after, we might leak a registration if the mutex was exited and the waiter // removed from the list prior to CancellationRegistration being properly assigned. By registering before, // there's a different race condition, that of cancellation being requested prior to storing the waiter into // the list; if that happens, we could end up adding the waiter and have it still stored in the list even // though OnCancellation was called. So once we hold the lock, which OnCancellation also needs to take, we // check again whether cancellation has been requested,and avoid storing the waiter if it has. w.CancellationRegistration = cancellationToken.UnsafeRegister((s, token) => OnCancellation(s, token), w); lock (SyncObj) { // Now that we're holding the lock, check to see whether the async lock is acquirable. if (!_lockedSemaphoreFull) { // If we are able to acquire the lock, we're done; we just need to clean up after the registration. w.CancellationRegistration.Unregister(); _lockedSemaphoreFull = true; return(Task.CompletedTask); } // Now that we're holding the lock and thus synchronized with OnCancellation, check to see // if cancellation has been requested. if (cancellationToken.IsCancellationRequested) { w.TrySetCanceled(cancellationToken); return(w.Task); } // The lock couldn't be acquired. // Add the waiter to the linked list of waiters. if (_waitersTail is null) { w.Next = w.Prev = w; } else { Debug.Assert(_waitersTail.Next != null && _waitersTail.Prev != null); w.Next = _waitersTail; w.Prev = _waitersTail.Prev; w.Prev.Next = w.Next.Prev = w; } _waitersTail = w; } // Return the waiter as a value task. return(w.Task);