// See the LICENSE file in the project root for more information.
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace System.Threading
/// </summary>
private int m_countOfWaitersPulsedToWake;
- // Dummy object used to in lock statements to protect the semaphore count, wait handle and cancelation
- private object? m_lockObj; // initialized non-null, then set to null on Dispose // TODO-NULLABLE: Consider using a separate field to track disposal
+ // Object used to synchronize access to state on the instance. The contained
+ // Boolean value indicates whether the instance has been disposed.
+ private readonly StrongBox<bool> m_lockObjAndDisposed;
// 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
return m_waitHandle;
//lock the count to avoid multiple threads initializing the handle if it is null
- lock (m_lockObj!)
+ lock (m_lockObjAndDisposed)
{
if (m_waitHandle == null)
{
}
m_maxCount = maxCount;
- m_lockObj = new object();
m_currentCount = initialCount;
+ m_lockObjAndDisposed = new StrongBox<bool>();
}
#endregion
//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_lockObj and (2)cts.internalLock
+ // occur for (1)this.m_lockObjAndDisposed and (2)cts.internalLock
CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.UnsafeRegister(s_cancellationTokenCanceledEventHandler, this);
try
{
try { }
finally
{
- Monitor.Enter(m_lockObj!, ref lockTaken);
+ Monitor.Enter(m_lockObjAndDisposed, ref lockTaken);
if (lockTaken)
{
m_waitCount++;
if (lockTaken)
{
m_waitCount--;
- Monitor.Exit(m_lockObj!);
+ Monitor.Exit(m_lockObjAndDisposed);
}
// Unregister the cancellation callback.
}
}
// ** the actual wait **
- bool waitSuccessful = Monitor.Wait(m_lockObj!, remainingWaitMilliseconds);
+ bool waitSuccessful = Monitor.Wait(m_lockObjAndDisposed, remainingWaitMilliseconds);
// This waiter has woken up and this needs to be reflected in the count of waiters pulsed to wake. Since we
// don't have thread-specific pulse state, there is not enough information to tell whether this thread woke up
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled<bool>(cancellationToken);
- lock (m_lockObj!)
+ lock (m_lockObjAndDisposed)
{
// If there are counts available, allow this waiter to succeed.
if (m_currentCount > 0)
/// <returns>The created task.</returns>
private TaskNode CreateAndAddAsyncWaiter()
{
- Debug.Assert(Monitor.IsEntered(m_lockObj!), "Requires the lock be held");
+ Debug.Assert(Monitor.IsEntered(m_lockObjAndDisposed), "Requires the lock be held");
// Create the task
var task = new TaskNode();
private bool RemoveAsyncWaiter(TaskNode task)
{
Debug.Assert(task != null, "Expected non-null task");
- Debug.Assert(Monitor.IsEntered(m_lockObj!), "Requires the lock be held");
+ Debug.Assert(Monitor.IsEntered(m_lockObjAndDisposed), "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;
private async Task<bool> 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");
+ Debug.Assert(Monitor.IsEntered(m_lockObjAndDisposed), "Requires the lock be held");
if (millisecondsTimeout != Timeout.Infinite)
{
// 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!)
+ lock (m_lockObjAndDisposed)
{
// 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,
}
int returnCount;
- lock (m_lockObj!)
+ lock (m_lockObjAndDisposed)
{
// Read the m_currentCount into a local variable to avoid unnecessary volatile accesses inside the lock.
int currentCount = m_currentCount;
m_countOfWaitersPulsedToWake += waitersToNotify;
for (int i = 0; i < waitersToNotify; i++)
{
- Monitor.Pulse(m_lockObj!);
+ Monitor.Pulse(m_lockObjAndDisposed);
}
}
{
if (disposing)
{
- if (m_waitHandle != null)
+ WaitHandle? wh = m_waitHandle;
+ if (wh != null)
{
- m_waitHandle.Dispose();
+ wh.Dispose();
m_waitHandle = null;
}
- m_lockObj = null!;
+
+ m_lockObjAndDisposed.Value = true;
+
m_asyncHead = null;
m_asyncTail = null;
}
{
Debug.Assert(obj is SemaphoreSlim, "Expected a SemaphoreSlim");
SemaphoreSlim semaphore = (SemaphoreSlim)obj;
- lock (semaphore.m_lockObj!)
+ lock (semaphore.m_lockObjAndDisposed)
{
- Monitor.PulseAll(semaphore.m_lockObj!); //wake up all waiters.
+ Monitor.PulseAll(semaphore.m_lockObjAndDisposed); //wake up all waiters.
}
}
/// </summary>
private void CheckDispose()
{
- if (m_lockObj == null)
+ if (m_lockObjAndDisposed.Value)
{
throw new ObjectDisposedException(null, SR.SemaphoreSlim_Disposed);
}