public bool Unregister(WaitHandle waitObject) { // Hold the lock during the synchronous part of Unregister (as in CoreCLR) using (LockHolder.Hold(_lock)) { if (!_unregistering) { // Ensure callbacks will not call SetThreadpoolWait anymore _unregistering = true; // Cease queueing more callbacks Interop.Kernel32.SetThreadpoolWait(_tpWait, IntPtr.Zero, IntPtr.Zero); // Should we wait for callbacks synchronously? Note that we treat the zero handle as the asynchronous case. SafeWaitHandle safeWaitHandle = waitObject?.SafeWaitHandle; bool blocking = ((safeWaitHandle != null) && (safeWaitHandle.DangerousGetHandle() == new IntPtr(-1))); if (blocking) { FinishUnregistering(); } else { // Wait for callbacks and dispose resources asynchronously ThreadPool.QueueUserWorkItem(FinishUnregisteringAsync, safeWaitHandle); } return(true); } } return(false); }
internal bool Change(uint dueTime, uint period) { bool success; using (LockHolder.Hold(TimerQueue.Instance.Lock)) { if (_canceled) { throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic); } m_period = period; if (dueTime == Timeout.UnsignedInfinite) { TimerQueue.Instance.DeleteTimer(this); success = true; } else { success = TimerQueue.Instance.UpdateTimer(this, dueTime, period); } } return(success); }
public bool Close(WaitHandle toSignal) { bool success; bool shouldSignal = false; using (LockHolder.Hold(TimerQueue.Instance.Lock)) { if (_canceled) { success = false; } else { _canceled = true; _notifyWhenNoCallbacksRunning = toSignal; TimerQueue.Instance.DeleteTimer(this); if (_callbacksRunning == 0) { shouldSignal = true; } success = true; } } if (shouldSignal) { SignalNoCallbacksRunning(); } return(success); }
/// <summary> /// Resizes a table to a certain length (or larger). /// </summary> private void GrowTable(ref LinkedSlotVolatile[] table, int minLength) { Debug.Assert(table.Length < minLength); // Determine the size of the new table and allocate it. int newLen = GetNewTableSize(minLength); LinkedSlotVolatile[] newTable = new LinkedSlotVolatile[newLen]; // // The lock is necessary to avoid a race with ThreadLocal.Dispose. GrowTable has to point all // LinkedSlot instances referenced in the old table to reference the new table. Without locking, // Dispose could use a stale SlotArray reference and clear out a slot in the old array only, while // the value continues to be referenced from the new (larger) array. // using (LockHolder.Hold(s_idManager.m_lock)) { for (int i = 0; i < table.Length; i++) { LinkedSlot linkedSlot = table[i].Value; if (linkedSlot != null && linkedSlot.SlotArray != null) { linkedSlot.SlotArray = newTable; newTable[i] = table[i]; } } } table = newTable; }
internal int GetId() { using (LockHolder.Hold(m_lock)) { int availableId = m_nextIdToTry; while (availableId < m_freeIds.Count) { if (m_freeIds[availableId]) { break; } availableId++; } if (availableId == m_freeIds.Count) { m_freeIds.Add(false); } else { m_freeIds[availableId] = false; } m_nextIdToTry = availableId + 1; return(availableId); } }
internal int Add(T e) { while (true) { T[] array = m_array; using (LockHolder.Hold(m_lock)) { for (int i = 0; i < array.Length; i++) { if (array[i] == null) { Volatile.Write(ref array[i], e); return(i); } else if (i == array.Length - 1) { // Must resize. If we raced and lost, we start over again. if (array != m_array) { continue; } T[] newArray = new T[array.Length * 2]; Array.Copy(array, newArray, i + 1); newArray[i + 1] = e; m_array = newArray; return(i + 1); } } } } }
/// <summary> /// Scans the table and frees all dead entries without adding them to the free entry list. /// Runs on the finalizer thread. /// </summary> private static void FreeDeadEntries() { // Be cautious as this method may run in parallel with grabbing a free entry in the // AssignEntry method. The potential race is checking IsAllocated && (Target == null) // while a new non-zero (allocated) GCHandle is being assigned to the Owner field // containing a zero (non-allocated) GCHandle. That must be safe as a GCHandle is // just an IntPtr, which is assigned atomically, and Target has load dependency on it. using (LockHolder.Hold(s_usedEntriesLock)) { // We do not care if the s_unusedEntryIndex value is stale here; it suffices that // the s_entries reference is locked and s_unusedEntryIndex points within that array. Debug.Assert(s_unusedEntryIndex <= s_entries.Length); for (int idx = s_unusedEntryIndex; --idx > 0;) { bool allocated = s_entries[idx].Owner.IsAllocated; if (allocated && (s_entries[idx].Owner.Target == null)) { s_entries[idx].Lock = null; s_entries[idx].Next = 0; s_entries[idx].Owner.Free(); } } } }
/// <summary> /// Scans the table and recycles all dead and freed entries adding them to the free entry /// list. Returns the number of recycled entries. /// </summary> private static int RecycleDeadEntries() { Debug.Assert(s_freeEntriesLock.IsAcquired); using (LockHolder.Hold(s_usedEntriesLock)) { int recycledEntries = 0; for (int idx = s_unusedEntryIndex; --idx > 0;) { bool freed = !s_entries[idx].Owner.IsAllocated; if (freed || (s_entries[idx].Owner.Target == null)) { s_entries[idx].Lock = null; s_entries[idx].Next = s_freeEntryList; if (!freed) { s_entries[idx].Owner.Free(); } s_freeEntryList = idx; recycledEntries++; } } return(recycledEntries); } }
public bool TrySetApartmentStateUnchecked(ApartmentState state) { if (this != CurrentThread) { using (LockHolder.Hold(_lock)) { if (HasStarted()) { throw new ThreadStateException(); } _initialApartmentState = state; return(true); } } 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; return(state == GetApartmentState()); }
public ValueTask CloseAsync() { using (LockHolder.Hold(TimerQueue.Instance.Lock)) { object notifyWhenNoCallbacksRunning = _notifyWhenNoCallbacksRunning; // Mark the timer as canceled if it's not already. if (_canceled) { if (notifyWhenNoCallbacksRunning is WaitHandle) { // A previous call to Close(WaitHandle) stored a WaitHandle. We could try to deal with // this case by using ThreadPool.RegisterWaitForSingleObject to create a Task that'll // complete when the WaitHandle is set, but since arbitrary WaitHandle's can be supplied // by the caller, it could be for an auto-reset event or similar where that caller's // WaitOne on the WaitHandle could prevent this wrapper Task from completing. We could also // change the implementation to support storing multiple objects, but that's not pay-for-play, // and the existing Close(WaitHandle) already discounts this as being invalid, instead just // returning false if you use it multiple times. Since first calling Timer.Dispose(WaitHandle) // and then calling Timer.DisposeAsync is not something anyone is likely to or should do, we // simplify by just failing in that case. return(new ValueTask(Task.FromException(new InvalidOperationException(SR.InvalidOperation_TimerAlreadyClosed)))); } } else { _canceled = true; TimerQueue.Instance.DeleteTimer(this); } // We've deleted the timer, so if there are no callbacks queued or running, // we're done and return an already-completed value task. if (_callbacksRunning == 0) { return(default);
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); }
public void Close() { using (LockHolder.Hold(TimerQueue.Instance.Lock)) { if (!m_canceled) { m_canceled = true; TimerQueue.Instance.DeleteTimer(this); } } }
// 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; } } }
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 } }
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; } } } }
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 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); } }
/// <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)); }
/// <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> /// 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); }
/// <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(); } }
// // 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> /// 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); }