private static void CancellationTokenCanceledEventHandler(object obj) { SemaphoreSlim semaphore = obj as SemaphoreSlim; Debug.Assert(semaphore != null, "Expected a SemaphoreSlim"); using (LockHolder.Hold(semaphore.m_lock)) { semaphore.m_condition.SignalAll(); //wake up all waiters. } }
private bool SetApartmentStateUnchecked(ApartmentState state, bool throwOnError) { ApartmentState retState; if (this != CurrentThread) { using (LockHolder.Hold(_lock)) { if (HasStarted()) { throw new ThreadStateException(); } // Compat: Disallow resetting the initial apartment state if (_initialApartmentState == ApartmentState.Unknown) { _initialApartmentState = state; } retState = _initialApartmentState; } } else { if ((t_comState & ComState.Locked) == 0) { if (state != ApartmentState.Unknown) { InitializeCom(state); } else { UninitializeCom(); } } // Clear the cache and check whether new state matches the desired state t_apartmentType = ApartmentType.Unknown; retState = GetApartmentState(); } if (retState != state) { if (throwOnError) { string msg = SR.Format(SR.Thread_ApartmentState_ChangeFailed, retState); throw new InvalidOperationException(msg); } return(false); } return(true); }
private static void CancellationTokenCallback(object obj) { ManualResetEventSlim mre = obj as ManualResetEventSlim; Debug.Assert(mre != null, "Expected a ManualResetEventSlim"); Debug.Assert(mre.m_lock != null); //the lock should have been created before this callback is registered for use. using (LockHolder.Hold(mre.m_lock)) { mre.m_condition.SignalAll(); // awaken all waiters } }
// Return an ID to the pool internal void ReturnId(int id) { using (LockHolder.Hold(m_lock)) { m_freeIds[id] = true; if (id < m_nextIdToTry) { m_nextIdToTry = id; } } }
public void Close() { using (LockHolder.Hold(TimerQueue.Instance.Lock)) { if (!m_canceled) { m_canceled = true; TimerQueue.Instance.DeleteTimer(this); } } }
volatile int m_pauseTicks = 0; // Time when Pause was called internal void Pause() { using (LockHolder.Hold(Lock)) { // Delete the native timer so that no timers are fired in the Pause zone if (m_appDomainTimer != null && !m_appDomainTimer.IsInvalid) { m_appDomainTimer.Dispose(); m_appDomainTimer = null; m_isAppDomainTimerScheduled = false; m_pauseTicks = TickCount; } } }
/// <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 }
internal void Remove(T e) { T[] array = m_array; using (LockHolder.Hold(m_lock)) { for (int i = 0; i < m_array.Length; i++) { if (m_array[i] == e) { Volatile.Write(ref m_array[i], null); break; } } } }
public static void ResetEvent(WaitableObject waitableObject) { Debug.Assert(waitableObject != null); LockHolder lockHolder = new LockHolder(s_lock); try { waitableObject.UnsignalEvent(ref lockHolder); } finally { lockHolder.Dispose(); } }
public void UnsignalEvent(ref LockHolder lockHolder) { s_lock.VerifyIsLocked(); if (!IsEvent) { lockHolder.Dispose(); WaitHandle.ThrowInvalidHandleException(); } if (IsSignaled) { --_signalCount; } }
public static void ReleaseMutex(WaitableObject waitableObject) { Debug.Assert(waitableObject != null); LockHolder lockHolder = new LockHolder(s_lock); try { waitableObject.SignalMutex(ref lockHolder); } finally { lockHolder.Dispose(); } }
private void StartInternal(object parameter) { using (LockHolder.Hold(_lock)) { if (!GetThreadStateBit(ThreadState.Unstarted)) { throw new ThreadStateException(SR.ThreadState_AlreadyStarted); } bool waitingForThreadStart = false; GCHandle threadHandle = GCHandle.Alloc(this); _threadStartArg = parameter; try { if (!CreateThread(threadHandle)) { throw new OutOfMemoryException(); } // Skip cleanup if any asynchronous exception happens while waiting for the thread start waitingForThreadStart = true; // Wait until the new thread either dies or reports itself as started while (GetThreadStateBit(ThreadState.Unstarted) && !JoinInternal(0)) { Yield(); } waitingForThreadStart = false; } finally { Debug.Assert(!waitingForThreadStart, "Leaked threadHandle"); if (!waitingForThreadStart) { threadHandle.Free(); _threadStartArg = null; } } if (GetThreadStateBit(ThreadState.Unstarted)) { // Lack of memory is the only expected reason for thread creation failure throw new ThreadStartException(new OutOfMemoryException()); } } }
public static int ReleaseSemaphore(WaitableObject waitableObject, int count) { Debug.Assert(waitableObject != null); Debug.Assert(count > 0); LockHolder lockHolder = new LockHolder(s_lock); try { return(waitableObject.SignalSemaphore(count, ref lockHolder)); } finally { lockHolder.Dispose(); } }
public void Close() { using (LockHolder.Hold(TimerQueue.Instance.Lock)) { // prevent ThreadAbort while updating state try { } finally { if (!m_canceled) { m_canceled = true; TimerQueue.Instance.DeleteTimer(this); } } } }
/// <summary> /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, /// using a 32-bit signed integer to measure the time interval, /// while observing a <see cref="T:System.Threading.CancellationToken"/>. /// </summary> /// <param name="millisecondsTimeout"> /// The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely. /// </param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to observe.</param> /// <returns> /// A task that will complete with a result of true if the current thread successfully entered /// the <see cref="SemaphoreSlim"/>, otherwise with a result of false. /// </returns> /// <exception cref="T:System.ObjectDisposedException">The current instance has already been /// disposed.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a negative number other than -1, /// which represents an infinite time-out. /// </exception> public Task <bool> WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken) { CheckDispose(); // Validate input if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException( nameof(millisecondsTimeout), millisecondsTimeout, SR.SemaphoreSlim_Wait_TimeoutWrong); } // Bail early for cancellation if (cancellationToken.IsCancellationRequested) { return(Task.FromCancellation <bool>(cancellationToken)); } using (LockHolder.Hold(m_lock)) { // If there are counts available, allow this waiter to succeed. if (m_currentCount > 0) { --m_currentCount; if (m_waitHandle != null && m_currentCount == 0) { m_waitHandle.Reset(); } return(s_trueTask); } else if (millisecondsTimeout == 0) { // No counts, if timeout is zero fail fast return(s_falseTask); } // If there aren't, create and return a task to the caller. // The task will be completed either when they've successfully acquired // the semaphore or when the timeout expired or cancellation was requested. else { Debug.Assert(m_currentCount == 0, "m_currentCount should never be negative"); var asyncWaiter = CreateAndAddAsyncWaiter(); return((millisecondsTimeout == Timeout.Infinite && !cancellationToken.CanBeCanceled) ? asyncWaiter : WaitUntilCountOrTimeoutAsync(asyncWaiter, millisecondsTimeout, cancellationToken)); } } }
/// <summary> /// Sets the hash code in a thread-safe way. /// </summary> public static int SetHashCode(int syncIndex, int hashCode) { Debug.Assert((0 < syncIndex) && (syncIndex < s_unusedEntryIndex)); // Acquire the lock to ensure we are updating the latest version of s_entries. This // lock may be avoided if we store the hash code and Monitor synchronization data in // the same object accessed by a reference. using (LockHolder.Hold(s_usedEntriesLock)) { int currentHash = s_entries[syncIndex].HashCode; if (currentHash != 0) { return(currentHash); } s_entries[syncIndex].HashCode = hashCode; return(hashCode); } }
/// <summary> /// Grows the sync table. If memory is not available, it throws an OOM exception keeping /// the state valid. /// </summary> private static void Grow() { Debug.Assert(s_freeEntriesLock.IsAcquired); int oldSize = s_entries.Length; int newSize = CalculateNewSize(oldSize); Entry[] newEntries = new Entry[newSize]; using (LockHolder.Hold(s_usedEntriesLock)) { // Copy the shallow content of the table Array.Copy(s_entries, newEntries, oldSize); // Publish the new table. Lock-free reader threads must not see the new value of // s_entries until all the content is copied to the new table. Volatile.Write(ref s_entries, newEntries); } }
public int SignalSemaphore(int count, ref LockHolder lockHolder) { s_lock.VerifyIsLocked(); Debug.Assert(count > 0); if (!IsSemaphore) { lockHolder.Dispose(); WaitHandle.ThrowInvalidHandleException(); } int oldSignalCount = _signalCount; Debug.Assert(oldSignalCount <= _maximumSignalCount); if (count > _maximumSignalCount - oldSignalCount) { lockHolder.Dispose(); throw new SemaphoreFullException(); } if (oldSignalCount != 0) { _signalCount = oldSignalCount + count; return(oldSignalCount); } for (ThreadWaitInfo.WaitedListNode?waiterNode = _waitersHead, nextWaiterNode; waiterNode != null; waiterNode = nextWaiterNode) { // Signaling the waiter will unregister the waiter node, so keep the next node before trying nextWaiterNode = waiterNode.NextThread; if (waiterNode.WaitInfo.TrySignalToSatisfyWait(waiterNode, isAbandonedMutex: false) && --count == 0) { return(oldSignalCount); } } _signalCount = count; return(oldSignalCount); }
public void SignalEvent(ref LockHolder lockHolder) { s_lock.VerifyIsLocked(); switch (_type) { case WaitableObjectType.ManualResetEvent: SignalManualResetEvent(); break; case WaitableObjectType.AutoResetEvent: SignalAutoResetEvent(); break; default: lockHolder.Dispose(); WaitHandle.ThrowInvalidHandleException(); break; } }
/// <summary> /// Assigns a sync table entry to the object in a thread-safe way. /// </summary> public static unsafe int AssignEntry(object obj, int *pHeader) { // Allocate the synchronization object outside the lock Lock lck = new Lock(); DeadEntryCollector collector = new DeadEntryCollector(); DependentHandle handle = new DependentHandle(obj, collector); try { using (LockHolder.Hold(s_lock)) { // After acquiring the lock check whether another thread already assigned the sync entry if (ObjectHeader.GetSyncEntryIndex(*pHeader, out int hashOrIndex)) { return(hashOrIndex); } int syncIndex; if (s_freeEntryList != 0) { // Grab a free entry from the list syncIndex = s_freeEntryList; ref Entry freeEntry = ref s_entries[syncIndex]; s_freeEntryList = freeEntry.Next; freeEntry.Next = 0; } else { if (s_unusedEntryIndex >= s_entries.Length) { // No free entries, use the slow path. This call may OOM. Grow(); } // Grab the next unused entry Debug.Assert(s_unusedEntryIndex < s_entries.Length); syncIndex = s_unusedEntryIndex++; } ref Entry entry = ref s_entries[syncIndex];
/// <summary>Performs the asynchronous wait.</summary> /// <param name="millisecondsTimeout">The timeout.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task to return to the caller.</returns> private async Task <bool> WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken) { Debug.Assert(asyncWaiter != null, "Waiter should have been constructed"); Debug.Assert(m_lock.IsAcquired, "Requires the lock be held"); // Wait until either the task is completed, timeout occurs, or cancellation is requested. // We need to ensure that the Task.Delay task is appropriately cleaned up if the await // completes due to the asyncWaiter completing, so we use our own token that we can explicitly // cancel, and we chain the caller's supplied token into it. using (var cts = cancellationToken.CanBeCanceled ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, default(CancellationToken)) : new CancellationTokenSource()) { var waitCompleted = Task.WhenAny(asyncWaiter, Task.Delay(millisecondsTimeout, cts.Token)); if (asyncWaiter == await waitCompleted.ConfigureAwait(false)) { cts.Cancel(); // ensure that the Task.Delay task is cleaned up return(true); // successfully acquired } } // If we get here, the wait has timed out or been canceled. // If the await completed synchronously, we still hold the lock. If it didn't, // we no longer hold the lock. As such, acquire it. using (LockHolder.Hold(m_lock)) { // Remove the task from the list. If we're successful in doing so, // we know that no one else has tried to complete this waiter yet, // so we can safely cancel or timeout. if (RemoveAsyncWaiter(asyncWaiter)) { cancellationToken.ThrowIfCancellationRequested(); // cancellation occurred return(false); // timeout occurred } } // The waiter had already been removed, which means it's already completed or is about to // complete, so let it, and don't return until it does. return(await asyncWaiter.ConfigureAwait(false)); }
public void SignalMutex(ref LockHolder lockHolder) { s_lock.VerifyIsLocked(); if (!IsMutex) { lockHolder.Dispose(); WaitHandle.ThrowInvalidHandleException(); } if (IsSignaled || _ownershipInfo !.Thread != Thread.CurrentThread) { lockHolder.Dispose(); throw new ApplicationException(SR.Arg_SynchronizationLockException); } if (!_ownershipInfo !.TryDecrementReacquireCount()) { SignalMutex(isAbandoned: false); } }
/// <summary> /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance. /// </summary> /// <param name="disposing"> /// A Boolean value that indicates whether this method is being called due to a call to <see cref="Dispose()"/>. /// </param> /// <remarks> /// Unlike most of the members of <see cref="T:System.Threading.ThreadLocal{T}"/>, this method is not thread-safe. /// </remarks> protected virtual void Dispose(bool disposing) { int id; using (LockHolder.Hold(s_idManager.m_lock)) { id = ~m_idComplement; m_idComplement = 0; if (id < 0 || !m_initialized) { Debug.Assert(id >= 0 || !m_initialized, "expected id >= 0 if initialized"); // Handle double Dispose calls or disposal of an instance whose constructor threw an exception. return; } m_initialized = false; for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next) { LinkedSlotVolatile[] slotArray = linkedSlot.SlotArray; if (slotArray == null) { // The thread that owns this slotArray has already finished. continue; } // Remove the reference from the LinkedSlot to the slot table. linkedSlot.SlotArray = null; // And clear the references from the slot table to the linked slot and the value so that // both can get garbage collected. slotArray[id].Value.Value = default(T); slotArray[id].Value = null; } } m_linkedSlot = null; s_idManager.ReturnId(id); }
public static int SignalAndWait( WaitableObject waitableObjectToSignal, WaitableObject waitableObjectToWaitOn, int timeoutMilliseconds, bool interruptible = true, bool prioritize = false) { Debug.Assert(waitableObjectToSignal != null); Debug.Assert(waitableObjectToWaitOn != null); Debug.Assert(timeoutMilliseconds >= -1); ThreadWaitInfo waitInfo = Thread.CurrentThread.WaitInfo; LockHolder lockHolder = new LockHolder(s_lock); try { // A pending interrupt does not signal the specified handle if (interruptible && waitInfo.CheckAndResetPendingInterrupt) { lockHolder.Dispose(); throw new ThreadInterruptedException(); } try { waitableObjectToSignal.Signal(1, ref lockHolder); } catch (SemaphoreFullException ex) { s_lock.VerifyIsNotLocked(); throw new InvalidOperationException(SR.Threading_WaitHandleTooManyPosts, ex); } return(waitableObjectToWaitOn.Wait_Locked(waitInfo, timeoutMilliseconds, interruptible, prioritize, ref lockHolder)); } finally { lockHolder.Dispose(); } }
/// <summary> /// Creates a LinkedSlot and inserts it into the linked list for this ThreadLocal instance. /// </summary> private void CreateLinkedSlot(LinkedSlotVolatile[] slotArray, int id, T value) { // Create a LinkedSlot var linkedSlot = new LinkedSlot(slotArray); // Insert the LinkedSlot into the linked list maintained by this ThreadLocal<> instance and into the slot array using (LockHolder.Hold(s_idManager.m_lock)) { // Check that the instance has not been disposed. It is important to check this under a lock, since // Dispose also executes under a lock. if (!m_initialized) { throw new ObjectDisposedException(SR.ThreadLocal_Disposed); } LinkedSlot firstRealNode = m_linkedSlot.Next; // // Insert linkedSlot between nodes m_linkedSlot and firstRealNode. // (m_linkedSlot is the dummy head node that should always be in the front.) // linkedSlot.Next = firstRealNode; linkedSlot.Previous = m_linkedSlot; linkedSlot.Value = value; if (firstRealNode != null) { firstRealNode.Previous = linkedSlot; } m_linkedSlot.Next = linkedSlot; // Assigning the slot under a lock prevents a race with Dispose (dispose also acquires the lock). // Otherwise, it would be possible that the ThreadLocal instance is disposed, another one gets created // with the same ID, and the write would go to the wrong instance. slotArray[id].Value = linkedSlot; } }
internal void Fire() { bool canceled = false; lock (TimerQueue.Instance) { canceled = _canceled; if (!canceled) { _callbacksRunning++; } } if (canceled) { return; } CallCallback(); bool shouldSignal = false; using (LockHolder.Hold(TimerQueue.Instance.Lock)) { _callbacksRunning--; if (_canceled && _callbacksRunning == 0 && _notifyWhenNoCallbacksRunning != null) { shouldSignal = true; } } if (shouldSignal) { SignalNoCallbacksRunning(); } }
public int Wait(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool interruptible, bool prioritize) { Debug.Assert(waitInfo != null); Debug.Assert(waitInfo.Thread == Thread.CurrentThread); Debug.Assert(timeoutMilliseconds >= -1); var lockHolder = new LockHolder(s_lock); try { if (interruptible && waitInfo.CheckAndResetPendingInterrupt) { lockHolder.Dispose(); throw new ThreadInterruptedException(); } return(Wait_Locked(waitInfo, timeoutMilliseconds, interruptible, prioritize, ref lockHolder)); } finally { lockHolder.Dispose(); } }
// // Fire any timers that have expired, and update the native timer to schedule the rest of them. // private void FireNextTimers() { // // we fire the first timer on this thread; any other timers that might have fired are queued // to the ThreadPool. // TimerQueueTimer timerToFireOnThisThread = null; using (LockHolder.Hold(Lock)) { // // since we got here, that means our previous timer has fired. // ReleaseTimer(); m_currentNativeTimerDuration = UInt32.MaxValue; bool haveTimerToSchedule = false; uint nextAppDomainTimerDuration = uint.MaxValue; int nowTicks = TickCount; // // Sweep through all timers. The ones that have reached their due time // will fire. We will calculate the next native timer due time from the // other timers. // TimerQueueTimer timer = m_timers; while (timer != null) { Debug.Assert(timer.m_dueTime != Timer.UnsignedInfiniteTimeout); uint elapsed = (uint)(nowTicks - timer.m_startTicks); if (elapsed >= timer.m_dueTime) { // // Remember the next timer in case we delete this one // TimerQueueTimer nextTimer = timer.m_next; if (timer.m_period != Timer.UnsignedInfiniteTimeout) { timer.m_startTicks = nowTicks; timer.m_dueTime = timer.m_period; // // This is a repeating timer; schedule it to run again. // if (timer.m_dueTime < nextAppDomainTimerDuration) { haveTimerToSchedule = true; nextAppDomainTimerDuration = timer.m_dueTime; } } else { // // Not repeating; remove it from the queue // DeleteTimer(timer); } // // If this is the first timer, we'll fire it on this thread. Otherwise, queue it // to the ThreadPool. // if (timerToFireOnThisThread == null) { timerToFireOnThisThread = timer; } else { QueueTimerCompletion(timer); } timer = nextTimer; } else { // // This timer hasn't fired yet. Just update the next time the native timer fires. // uint remaining = timer.m_dueTime - elapsed; if (remaining < nextAppDomainTimerDuration) { haveTimerToSchedule = true; nextAppDomainTimerDuration = remaining; } timer = timer.m_next; } } if (haveTimerToSchedule) { EnsureAppDomainTimerFiresBy(nextAppDomainTimerDuration); } } // // Fire the user timer outside of the lock! // if (timerToFireOnThisThread != null) { timerToFireOnThisThread.Fire(); } }
/// <summary> /// Attempt to acquire a lock on the queue to mark it as in-use. /// </summary> /// <param name="lockObject">If locking the queue for use was /// successful (returned true), lockObject is an IDisposable that /// will discard the lock when disposed.</param> /// <returns>True if the queue is now marked as in-use, false if the /// queue could not be marked as in-use due to being blocked (or /// one of its lockqueues was in-use).</returns> public bool TryLock(out IDisposable lockObject) { Log.Info(string.Format("Queue: '{0}' is attempting to be in-use, trying to lock related queues", Name)); lockObject = null; lock (blockingLockObject) { if (IsBlocked) { Log.Info(string.Format("Queue: '{0}' is locked and cannot be in-use", Name)); return false; } IList<IIntegrationQueue> lockedQueues = new List<IIntegrationQueue>(); bool failed = false; foreach (IIntegrationQueue queue in LockQueues) { if (queue.BlockQueue(this)) { Log.Info(string.Format("Queue: '{0}' has acquired a lock against queue '{1}'", Name, queue.Name)); lockedQueues.Add(queue); } else { Log.Info(string.Format("Queue: '{0}' has FAILED to acquire a lock against queue '{1}'", Name, queue.Name)); failed = true; break; } } if (failed) { foreach (IIntegrationQueue queue in lockedQueues) { Log.Info(string.Format("Queue: '{0}' has released a lock against queue '{1}'", Name, queue.Name)); queue.UnblockQueue(this); return false; } } lockObject = new LockHolder(this, lockedQueues); inUse = true; return true; } }
/// <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( nameof(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; int waitersToNotify = Math.Min(releaseCount, waitCount); for (int i = 0; i < waitersToNotify; i++) { m_condition.SignalOne(); } // 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); }
/// <summary> /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a /// 32-bit signed integer to measure the time interval, while observing a <see /// cref="T:System.Threading.CancellationToken"/>. /// </summary> /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to /// observe.</param> /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise, /// false.</returns> /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a /// negative number other than -1, which represents an infinite time-out.</exception> /// <exception cref="T:System.InvalidOperationException"> /// The maximum number of waiters has been exceeded. /// </exception> /// <exception cref="T:System.Threading.OperationCanceledException"><paramref /// name="cancellationToken"/> was canceled.</exception> 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 HOW_MANY_SPIN_BEFORE_YIELD = 10; int HOW_MANY_YIELD_EVERY_SLEEP_0 = 5; int HOW_MANY_YIELD_EVERY_SLEEP_1 = 20; int spinCount = SpinCount; for (int i = 0; i < spinCount; i++) { if (IsSet) { return(true); } else if (i < HOW_MANY_SPIN_BEFORE_YIELD) { if (i == HOW_MANY_SPIN_BEFORE_YIELD / 2) { RuntimeThread.Yield(); } else { RuntimeThread.SpinWait(PlatformHelper.ProcessorCount * (4 << i)); } } else if (i % HOW_MANY_YIELD_EVERY_SLEEP_1 == 0) { RuntimeThread.Sleep(1); } else if (i % HOW_MANY_YIELD_EVERY_SLEEP_0 == 0) { RuntimeThread.Sleep(0); } else { RuntimeThread.Yield(); } if (i >= 100 && i % 10 == 0) // check the cancellation token if the user passed a very large spin count { cancellationToken.ThrowIfCancellationRequested(); } } // Now enter the lock and wait. EnsureLockObjectCreated(); // We must register and deregister the token outside of the lock, to avoid deadlocks. using (cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCallback, this)) { using (LockHolder.Hold(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 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 (!m_condition.Wait(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 deregisters) the callback return(true); //done. The wait was satisfied. }