// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
//
//
// A lightweight semahore class that contains the basic semaphore functions plus some useful functions like interrupt
// and wait handle exposing to allow waiting on multiple semaphores.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Diagnostics.Contracts;
using System.Threading.Tasks;
// The class will be part of the current System.Threading namespace
namespace System.Threading
{
///
/// Limits the number of threads that can access a resource or pool of resources concurrently.
///
///
///
/// The provides a lightweight semaphore class that doesn't
/// use Windows kernel semaphores.
///
///
/// All public and protected members of are thread-safe and may be used
/// concurrently from multiple threads, with the exception of Dispose, which
/// must only be used when all other operations on the have
/// completed.
///
///
[ComVisible(false)]
[DebuggerDisplay("Current Count = {m_currentCount}")]
public class SemaphoreSlim : IDisposable
{
#region Private Fields
// The semaphore count, initialized in the constructor to the initial value, every release call incremetns it
// and every wait call decrements it as long as its value is positive otherwise the wait will block.
// Its value must be between the maximum semaphore value and zero
private volatile int m_currentCount;
// The maximum semaphore value, it is initialized to Int.MaxValue if the client didn't specify it. it is used
// to check if the count excceeded the maxi value or not.
private readonly int m_maxCount;
// The number of synchronously waiting threads, it is set to zero in the constructor and increments before blocking the
// threading and decrements it back after that. It is used as flag for the release call to know if there are
// waiting threads in the monitor or not.
private volatile int m_waitCount;
// Dummy object used to in lock statements to protect the semaphore count, wait handle and cancelation
private object m_lockObj;
// Act as the semaphore wait handle, it's lazily initialized if needed, the first WaitHandle call initialize it
// and wait an release sets and resets it respectively as long as it is not null
private volatile ManualResetEvent m_waitHandle;
// Head of list representing asynchronous waits on the semaphore.
private TaskNode m_asyncHead;
// Tail of list representing asynchronous waits on the semaphore.
private TaskNode m_asyncTail;
// A pre-completed task with Result==true
private readonly static Task s_trueTask =
new Task(false, true, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken));
// A pre-completed task with Result==false
private readonly static Task s_falseTask =
new Task(false, false, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken));
// No maximum constant
private const int NO_MAXIMUM = Int32.MaxValue;
// Task in a linked list of asynchronous waiters
private sealed class TaskNode : Task, IThreadPoolWorkItem
{
internal TaskNode Prev, Next;
internal TaskNode() : base() {}
void IThreadPoolWorkItem.ExecuteWorkItem()
{
bool setSuccessfully = TrySetResult(true);
Debug.Assert(setSuccessfully, "Should have been able to complete task");
}
void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { /* nop */ }
}
#endregion
#region Public properties
///
/// Gets the current count of the .
///
/// The current count of the .
public int CurrentCount
{
get { return m_currentCount; }
}
///
/// Returns a that can be used to wait on the semaphore.
///
/// A that can be used to wait on the
/// semaphore.
///
/// A successful wait on the does not imply a successful wait on
/// the itself, nor does it decrement the semaphore's
/// count. exists to allow a thread to block waiting on multiple
/// semaphores, but such a wait should be followed by a true wait on the target semaphore.
///
/// The has been disposed.
public WaitHandle AvailableWaitHandle
{
get
{
CheckDispose();
// Return it directly if it is not null
if (m_waitHandle != null)
return m_waitHandle;
//lock the count to avoid multiple threads initializing the handle if it is null
lock (m_lockObj)
{
if (m_waitHandle == null)
{
// The initial state for the wait handle is true if the count is greater than zero
// false otherwise
m_waitHandle = new ManualResetEvent(m_currentCount != 0);
}
}
return m_waitHandle;
}
}
#endregion
#region Constructors
///
/// Initializes a new instance of the class, specifying
/// the initial number of requests that can be granted concurrently.
///
/// The initial number of requests for the semaphore that can be granted
/// concurrently.
///
/// is less than 0.
public SemaphoreSlim(int initialCount)
: this(initialCount, NO_MAXIMUM)
{
}
///
/// Initializes a new instance of the class, specifying
/// the initial and maximum number of requests that can be granted concurrently.
///
/// The initial number of requests for the semaphore that can be granted
/// concurrently.
/// The maximum number of requests for the semaphore that can be granted
/// concurrently.
///
/// is less than 0. -or-
/// is greater than . -or-
/// is less than 0.
public SemaphoreSlim(int initialCount, int maxCount)
{
if (initialCount < 0 || initialCount > maxCount)
{
throw new ArgumentOutOfRangeException(
nameof(initialCount), initialCount, GetResourceString("SemaphoreSlim_ctor_InitialCountWrong"));
}
//validate input
if (maxCount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maxCount), maxCount, GetResourceString("SemaphoreSlim_ctor_MaxCountWrong"));
}
m_maxCount = maxCount;
m_lockObj = new object();
m_currentCount = initialCount;
}
#endregion
#region Methods
///
/// Blocks the current thread until it can enter the .
///
/// The current instance has already been
/// disposed.
public void Wait()
{
// Call wait with infinite timeout
Wait(Timeout.Infinite, new CancellationToken());
}
///
/// Blocks the current thread until it can enter the , while observing a
/// .
///
/// The token to
/// observe.
/// was
/// canceled.
/// The current instance has already been
/// disposed.
public void Wait(CancellationToken cancellationToken)
{
// Call wait with infinite timeout
Wait(Timeout.Infinite, cancellationToken);
}
///
/// Blocks the current thread until it can enter the , using a to measure the time interval.
///
/// A that represents the number of milliseconds
/// to wait, or a that represents -1 milliseconds to wait indefinitely.
///
/// true if the current thread successfully entered the ;
/// otherwise, false.
/// is a negative
/// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
/// than .
public bool Wait(TimeSpan timeout)
{
// Validate the timeout
Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
{
throw new System.ArgumentOutOfRangeException(
nameof(timeout), timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
}
// Call wait with the timeout milliseconds
return Wait((int)timeout.TotalMilliseconds, new CancellationToken());
}
///
/// Blocks the current thread until it can enter the , using a to measure the time interval, while observing a .
///
/// A that represents the number of milliseconds
/// to wait, or a that represents -1 milliseconds to wait indefinitely.
///
/// The to
/// observe.
/// true if the current thread successfully entered the ;
/// otherwise, false.
/// is a negative
/// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
/// than .
/// was canceled.
public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
{
// Validate the timeout
Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
{
throw new System.ArgumentOutOfRangeException(
nameof(timeout), timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
}
// Call wait with the timeout milliseconds
return Wait((int)timeout.TotalMilliseconds, cancellationToken);
}
///
/// Blocks the current thread until it can enter the , using a 32-bit
/// signed integer to measure the time interval.
///
/// The number of milliseconds to wait, or (-1) to wait indefinitely.
/// true if the current thread successfully entered the ;
/// otherwise, false.
/// is a
/// negative number other than -1, which represents an infinite time-out.
public bool Wait(int millisecondsTimeout)
{
return Wait(millisecondsTimeout, new CancellationToken());
}
///
/// Blocks the current thread until it can enter the ,
/// using a 32-bit signed integer to measure the time interval,
/// while observing a .
///
/// The number of milliseconds to wait, or (-1) to
/// wait indefinitely.
/// The to observe.
/// true if the current thread successfully entered the ; otherwise, false.
/// is a negative number other than -1,
/// which represents an infinite time-out.
/// was canceled.
public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
{
CheckDispose();
// Validate input
if (millisecondsTimeout < -1)
{
throw new ArgumentOutOfRangeException(
"totalMilliSeconds", millisecondsTimeout, GetResourceString("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 asyncWaitTask = null;
bool lockTaken = false;
//Register for cancellation outside of the main lock.
//NOTE: Register/deregister inside the lock can deadlock as different lock acquisition orders could
// occur for (1)this.m_lockObj and (2)cts.internalLock
CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCanceledEventHandler, this);
try
{
// Perf: first spin wait for the count to be positive, but only up to the first planned yield.
// This additional amount of spinwaiting in addition
// to Monitor.Enter()’s spinwaiting has shown measurable perf gains in test scenarios.
//
SpinWait spin = new SpinWait();
while (m_currentCount == 0 && !spin.NextSpinWillYield)
{
spin.SpinOnce();
}
// entering the lock and incrementing waiters must not suffer a thread-abort, else we cannot
// clean up m_waitCount correctly, which may lead to deadlock due to non-woken waiters.
try { }
finally
{
Monitor.Enter(m_lockObj, ref lockTaken);
if (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_lockObj);
}
// 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;
}
///
/// Local helper function, waits on the monitor until the monitor recieves signal or the
/// timeout is expired
///
/// The maximum timeout
/// The start ticks to calculate the elapsed time
/// The CancellationToken to observe.
/// true if the monitor recieved a signal, false if the timeout expired
private bool WaitUntilCountOrTimeout(int millisecondsTimeout, uint startTime, CancellationToken cancellationToken)
{
int remainingWaitMilliseconds = Timeout.Infinite;
//Wait on the monitor as long as the count is zero
while (m_currentCount == 0)
{
// If cancelled, we throw. Trying to wait could lead to deadlock.
cancellationToken.ThrowIfCancellationRequested();
if (millisecondsTimeout != Timeout.Infinite)
{
remainingWaitMilliseconds = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
if (remainingWaitMilliseconds <= 0)
{
// The thread has expires its timeout
return false;
}
}
// ** the actual wait **
if (!Monitor.Wait(m_lockObj, remainingWaitMilliseconds))
{
return false;
}
}
return true;
}
///
/// Asynchronously waits to enter the .
///
/// A task that will complete when the semaphore has been entered.
public Task WaitAsync()
{
return WaitAsync(Timeout.Infinite, default(CancellationToken));
}
///
/// Asynchronously waits to enter the , while observing a
/// .
///
/// A task that will complete when the semaphore has been entered.
///
/// The token to observe.
///
///
/// The current instance has already been disposed.
///
public Task WaitAsync(CancellationToken cancellationToken)
{
return WaitAsync(Timeout.Infinite, cancellationToken);
}
///
/// Asynchronously waits to enter the ,
/// using a 32-bit signed integer to measure the time interval.
///
///
/// The number of milliseconds to wait, or (-1) to wait indefinitely.
///
///
/// A task that will complete with a result of true if the current thread successfully entered
/// the , otherwise with a result of false.
///
/// The current instance has already been
/// disposed.
/// is a negative number other than -1,
/// which represents an infinite time-out.
///
public Task WaitAsync(int millisecondsTimeout)
{
return WaitAsync(millisecondsTimeout, default(CancellationToken));
}
///
/// Asynchronously waits to enter the , using a to measure the time interval, while observing a
/// .
///
///
/// A that represents the number of milliseconds
/// to wait, or a that represents -1 milliseconds to wait indefinitely.
///
///
/// The token to observe.
///
///
/// A task that will complete with a result of true if the current thread successfully entered
/// the , otherwise with a result of false.
///
///
/// The current instance has already been disposed.
///
///
/// is a negative number other than -1 milliseconds, which represents
/// an infinite time-out -or- timeout is greater than .
///
public Task WaitAsync(TimeSpan timeout)
{
return WaitAsync(timeout, default(CancellationToken));
}
///
/// Asynchronously waits to enter the , using a to measure the time interval.
///
///
/// A that represents the number of milliseconds
/// to wait, or a that represents -1 milliseconds to wait indefinitely.
///
///
/// A task that will complete with a result of true if the current thread successfully entered
/// the , otherwise with a result of false.
///
///
/// is a negative number other than -1 milliseconds, which represents
/// an infinite time-out -or- timeout is greater than .
///
public Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
// Validate the timeout
Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
{
throw new System.ArgumentOutOfRangeException(
nameof(timeout), timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
}
// Call wait with the timeout milliseconds
return WaitAsync((int)timeout.TotalMilliseconds, cancellationToken);
}
///
/// Asynchronously waits to enter the ,
/// using a 32-bit signed integer to measure the time interval,
/// while observing a .
///
///
/// The number of milliseconds to wait, or (-1) to wait indefinitely.
///
/// The to observe.
///
/// A task that will complete with a result of true if the current thread successfully entered
/// the , otherwise with a result of false.
///
/// The current instance has already been
/// disposed.
/// is a negative number other than -1,
/// which represents an infinite time-out.
///
public Task WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken)
{
CheckDispose();
// Validate input
if (millisecondsTimeout < -1)
{
throw new ArgumentOutOfRangeException(
"totalMilliSeconds", millisecondsTimeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
}
// Bail early for cancellation
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled(cancellationToken);
lock (m_lockObj)
{
// 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);
}
}
}
/// Creates a new task and stores it into the async waiters list.
/// The created task.
private TaskNode CreateAndAddAsyncWaiter()
{
Debug.Assert(Monitor.IsEntered(m_lockObj), "Requires the lock be held");
// Create the task
var task = new TaskNode();
// Add it to the linked list
if (m_asyncHead == null)
{
Debug.Assert(m_asyncTail == null, "If head is null, so too should be tail");
m_asyncHead = task;
m_asyncTail = task;
}
else
{
Debug.Assert(m_asyncTail != null, "If head is not null, neither should be tail");
m_asyncTail.Next = task;
task.Prev = m_asyncTail;
m_asyncTail = task;
}
// Hand it back
return task;
}
/// Removes the waiter task from the linked list.
/// The task to remove.
/// true if the waiter was in the list; otherwise, false.
private bool RemoveAsyncWaiter(TaskNode task)
{
Contract.Requires(task != null, "Expected non-null task");
Debug.Assert(Monitor.IsEntered(m_lockObj), "Requires the lock be held");
// Is the task in the list? To be in the list, either it's the head or it has a predecessor that's in the list.
bool wasInList = m_asyncHead == task || task.Prev != null;
// Remove it from the linked list
if (task.Next != null) task.Next.Prev = task.Prev;
if (task.Prev != null) task.Prev.Next = task.Next;
if (m_asyncHead == task) m_asyncHead = task.Next;
if (m_asyncTail == task) m_asyncTail = task.Prev;
Debug.Assert((m_asyncHead == null) == (m_asyncTail == null), "Head is null iff tail is null");
// Make sure not to leak
task.Next = task.Prev = null;
// Return whether the task was in the list
return wasInList;
}
/// Performs the asynchronous wait.
/// The timeout.
/// The cancellation token.
/// The task to return to the caller.
private async Task WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken)
{
Debug.Assert(asyncWaiter != null, "Waiter should have been constructed");
Debug.Assert(Monitor.IsEntered(m_lockObj), "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.
lock (m_lockObj)
{
// 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);
}
///
/// Exits the once.
///
/// The previous count of the .
/// The current instance has already been
/// disposed.
public int Release()
{
return Release(1);
}
///
/// Exits the a specified number of times.
///
/// The number of times to exit the semaphore.
/// The previous count of the .
/// is less
/// than 1.
/// The has
/// already reached its maximum size.
/// The current instance has already been
/// disposed.
public int Release(int releaseCount)
{
CheckDispose();
// Validate input
if (releaseCount < 1)
{
throw new ArgumentOutOfRangeException(
nameof(releaseCount), releaseCount, GetResourceString("SemaphoreSlim_Release_CountWrong"));
}
int returnCount;
lock (m_lockObj)
{
// 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++)
{
Monitor.Pulse(m_lockObj);
}
// 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;
}
///
/// Queues a waiter task to the ThreadPool. We use this small helper method so that
/// the larger Release(count) method does not need to be SecuritySafeCritical.
///
private static void QueueWaiterTask(TaskNode waiterTask)
{
ThreadPool.UnsafeQueueCustomWorkItem(waiterTask, forceGlobal: false);
}
///
/// Releases all resources used by the current instance of .
///
///
/// Unlike most of the members of , is not
/// thread-safe and may not be used concurrently with other members of this instance.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// When overridden in a derived class, releases the unmanaged resources used by the
/// , and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources;
/// false to release only unmanaged resources.
///
/// Unlike most of the members of , is not
/// thread-safe and may not be used concurrently with other members of this instance.
///
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (m_waitHandle != null)
{
m_waitHandle.Close();
m_waitHandle = null;
}
m_lockObj = null;
m_asyncHead = null;
m_asyncTail = null;
}
}
///
/// Private helper method to wake up waiters when a cancellationToken gets canceled.
///
private static Action