/// <summary> /// Private helper to actually perform the Set. /// </summary> /// <param name="duringCancellation">Indicates whether we are calling Set() during cancellation.</param> /// <exception cref="T:System.OperationCanceledException">The object has been canceled.</exception> private void Set(bool duringCancellation) { // We need to ensure that IsSet=true does not get reordered past the read of m_eventObj // This would be a legal movement according to the .NET memory model. // The code is safe as IsSet involves an Interlocked.CompareExchange which provides a full memory barrier. IsSet = true; // If there are waiting threads, we need to pulse them. if (Waiters > 0) { Debug.Assert(m_lock != null && m_condition != null); //if waiters>0, then m_lock has already been created. using (LockHolder.Hold(m_lock)) { m_condition.SignalAll(); } } ManualResetEvent eventObj = m_eventObj; //Design-decision: do not set the event if we are in cancellation -> better to deadlock than to wake up waiters incorrectly //It would be preferable to wake up the event and have it throw OCE. This requires MRE to implement cancellation logic if (eventObj != null && !duringCancellation) { // We must surround this call to Set in a lock. The reason is fairly subtle. // Sometimes a thread will issue a Wait and wake up after we have set m_state, // but before we have gotten around to setting m_eventObj (just below). That's // because Wait first checks m_state and will only access the event if absolutely // necessary. However, the coding pattern { event.Wait(); event.Dispose() } is // quite common, and we must support it. If the waiter woke up and disposed of // the event object before the setter has finished, however, we would try to set a // now-disposed Win32 event. Crash! To deal with this race, we use a lock to // protect access to the event object when setting and disposing of it. We also // double-check that the event has not become null in the meantime when in the lock. lock (eventObj) { if (m_eventObj != null) { // If somebody is waiting, we must set the event. m_eventObj.Set(); } } } #if DEBUG m_lastSetTime = Environment.TickCount; #endif }
/// <summary> /// Exits the <see cref="SemaphoreSlim"/> a specified number of times. /// </summary> /// <param name="releaseCount">The number of times to exit the semaphore.</param> /// <returns>The previous count of the <see cref="SemaphoreSlim"/>.</returns> /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="releaseCount"/> is less /// than 1.</exception> /// <exception cref="T:System.Threading.SemaphoreFullException">The <see cref="SemaphoreSlim"/> has /// already reached its maximum size.</exception> /// <exception cref="T:System.ObjectDisposedException">The current instance has already been /// disposed.</exception> public int Release(int releaseCount) { CheckDispose(); // Validate input if (releaseCount < 1) { throw new ArgumentOutOfRangeException( "releaseCount", releaseCount, SR.SemaphoreSlim_Release_CountWrong); } int returnCount; using (LockHolder.Hold(m_lock)) { // Read the m_currentCount into a local variable to avoid unnecessary volatile accesses inside the lock. int currentCount = m_currentCount; returnCount = currentCount; // If the release count would result exceeding the maximum count, throw SemaphoreFullException. if (m_maxCount - currentCount < releaseCount) { throw new SemaphoreFullException(); } // Increment the count by the actual release count currentCount += releaseCount; // Signal to any synchronous waiters int waitCount = m_waitCount; if (currentCount == 1 || waitCount == 1) { m_condition.SignalOne(); } else if (waitCount > 1) { m_condition.SignalAll(); } // Now signal to any asynchronous waiters, if there are any. While we've already // signaled the synchronous waiters, we still hold the lock, and thus // they won't have had an opportunity to acquire this yet. So, when releasing // asynchronous waiters, we assume that all synchronous waiters will eventually // acquire the semaphore. That could be a faulty assumption if those synchronous // waits are canceled, but the wait code path will handle that. if (m_asyncHead != null) { Debug.Assert(m_asyncTail != null, "tail should not be null if head isn't null"); int maxAsyncToRelease = currentCount - waitCount; while (maxAsyncToRelease > 0 && m_asyncHead != null) { --currentCount; --maxAsyncToRelease; // Get the next async waiter to release and queue it to be completed var waiterTask = m_asyncHead; RemoveAsyncWaiter(waiterTask); // ensures waiterTask.Next/Prev are null QueueWaiterTask(waiterTask); } } m_currentCount = currentCount; // Exposing wait handle if it is not null if (m_waitHandle != null && returnCount == 0 && currentCount > 0) { m_waitHandle.Set(); } } // And return the count return(returnCount); }