<Compile Include="$(BclSourcesRoot)\System\Threading\WaitHandle.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\SpinLock.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\ThreadLocal.cs" />
- <Compile Include="$(BclSourcesRoot)\System\Threading\SemaphoreSlim.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\ManualResetEventSlim.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\CancellationTokenRegistration.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\CancellationTokenSource.cs" />
- <Compile Include="$(BclSourcesRoot)\System\Threading\CancellationToken.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\Tasks\future.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\Tasks\FutureFactory.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\Tasks\Task.cs" />
<Compile Include="$(BclSourcesRoot)\System\Collections\ObjectModel\ReadOnlyDictionary.cs" />
</ItemGroup>
<ItemGroup>
- <Compile Include="$(BclSourcesRoot)\Microsoft\Win32\SafeHandles\SafeWaitHandle.cs" />
<Compile Condition="'$(FeatureWin32Registry)' == 'true'" Include="$(BclSourcesRoot)\Microsoft\Win32\SafeHandles\SafeRegistryHandle.cs" />
</ItemGroup>
<ItemGroup>
+++ /dev/null
-// 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 wrapper for Win32 events (mutexes, auto reset events, and
-** manual reset events). Used by WaitHandle.
-**
-**
-===========================================================*/
-
-using System;
-using System.Security;
-using System.Runtime.InteropServices;
-using System.Runtime.CompilerServices;
-using System.Runtime.ConstrainedExecution;
-using System.Runtime.Versioning;
-using Microsoft.Win32;
-using System.Threading;
-
-namespace Microsoft.Win32.SafeHandles
-{
- public sealed class SafeWaitHandle : SafeHandleZeroOrMinusOneIsInvalid
- {
- // Called by P/Invoke marshaler
- private SafeWaitHandle() : base(true)
- {
- }
-
- public SafeWaitHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle)
- {
- SetHandle(existingHandle);
- }
-
- override protected bool ReleaseHandle()
- {
- return Win32Native.CloseHandle(handle);
- }
- }
-}
internal static extern uint SysStringByteLen(IntPtr bstr);
#endif
- [DllImport(Interop.Libraries.Kernel32, SetLastError = true)]
- internal static extern bool CloseHandle(IntPtr handle);
[DllImport(Interop.Libraries.Kernel32, SetLastError = true)]
internal static extern unsafe int WriteFile(SafeFileHandle handle, byte* bytes, int numBytesToWrite, out int numBytesWritten, IntPtr mustBeZero);
+++ /dev/null
-// 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.
-
-using System.Diagnostics;
-
-namespace System.Threading
-{
- /// <summary>
- /// Propagates notification that operations should be canceled.
- /// </summary>
- /// <remarks>
- /// <para>
- /// A <see cref="CancellationToken"/> may be created directly in an unchangeable canceled or non-canceled state
- /// using the CancellationToken's constructors. However, to have a CancellationToken that can change
- /// from a non-canceled to a canceled state,
- /// <see cref="System.Threading.CancellationTokenSource">CancellationTokenSource</see> must be used.
- /// CancellationTokenSource exposes the associated CancellationToken that may be canceled by the source through its
- /// <see cref="System.Threading.CancellationTokenSource.Token">Token</see> property.
- /// </para>
- /// <para>
- /// Once canceled, a token may not transition to a non-canceled state, and a token whose
- /// <see cref="CanBeCanceled"/> is false will never change to one that can be canceled.
- /// </para>
- /// <para>
- /// All members of this struct are thread-safe and may be used concurrently from multiple threads.
- /// </para>
- /// </remarks>
- [DebuggerDisplay("IsCancellationRequested = {IsCancellationRequested}")]
- public readonly struct CancellationToken
- {
- private readonly static Action<object> s_actionToActionObjShunt = obj => ((Action)obj)();
-
- // The backing TokenSource.
- // if null, it implicitly represents the same thing as new CancellationToken(false).
- // When required, it will be instantiated to reflect this.
- private readonly CancellationTokenSource _source;
- //!! warning. If more fields are added, the assumptions in CreateLinkedToken may no longer be valid
-
- /// <summary>
- /// Returns an empty CancellationToken value.
- /// </summary>
- /// <remarks>
- /// The <see cref="CancellationToken"/> value returned by this property will be non-cancelable by default.
- /// </remarks>
- public static CancellationToken None => default;
-
- /// <summary>
- /// Gets whether cancellation has been requested for this token.
- /// </summary>
- /// <value>Whether cancellation has been requested for this token.</value>
- /// <remarks>
- /// <para>
- /// This property indicates whether cancellation has been requested for this token,
- /// either through the token initially being constructed in a canceled state, or through
- /// calling <see cref="System.Threading.CancellationTokenSource.Cancel()">Cancel</see>
- /// on the token's associated <see cref="CancellationTokenSource"/>.
- /// </para>
- /// <para>
- /// If this property is true, it only guarantees that cancellation has been requested.
- /// It does not guarantee that every registered handler
- /// has finished executing, nor that cancellation requests have finished propagating
- /// to all registered handlers. Additional synchronization may be required,
- /// particularly in situations where related objects are being canceled concurrently.
- /// </para>
- /// </remarks>
- public bool IsCancellationRequested => _source != null && _source.IsCancellationRequested;
-
- /// <summary>
- /// Gets whether this token is capable of being in the canceled state.
- /// </summary>
- /// <remarks>
- /// If CanBeCanceled returns false, it is guaranteed that the token will never transition
- /// into a canceled state, meaning that <see cref="IsCancellationRequested"/> will never
- /// return true.
- /// </remarks>
- public bool CanBeCanceled => _source != null;
-
- /// <summary>
- /// Gets a <see cref="T:System.Threading.WaitHandle"/> that is signaled when the token is canceled.</summary>
- /// <remarks>
- /// Accessing this property causes a <see cref="T:System.Threading.WaitHandle">WaitHandle</see>
- /// to be instantiated. It is preferable to only use this property when necessary, and to then
- /// dispose the associated <see cref="CancellationTokenSource"/> instance at the earliest opportunity (disposing
- /// the source will dispose of this allocated handle). The handle should not be closed or disposed directly.
- /// </remarks>
- /// <exception cref="T:System.ObjectDisposedException">The associated <see
- /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
- public WaitHandle WaitHandle => (_source ?? CancellationTokenSource.s_neverCanceledSource).WaitHandle;
-
- // public CancellationToken()
- // this constructor is implicit for structs
- // -> this should behaves exactly as for new CancellationToken(false)
-
- /// <summary>
- /// Internal constructor only a CancellationTokenSource should create a CancellationToken
- /// </summary>
- internal CancellationToken(CancellationTokenSource source) => _source = source;
-
- /// <summary>
- /// Initializes the <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
- /// </summary>
- /// <param name="canceled">
- /// The canceled state for the token.
- /// </param>
- /// <remarks>
- /// Tokens created with this constructor will remain in the canceled state specified
- /// by the <paramref name="canceled"/> parameter. If <paramref name="canceled"/> is false,
- /// both <see cref="CanBeCanceled"/> and <see cref="IsCancellationRequested"/> will be false.
- /// If <paramref name="canceled"/> is true,
- /// both <see cref="CanBeCanceled"/> and <see cref="IsCancellationRequested"/> will be true.
- /// </remarks>
- public CancellationToken(bool canceled) : this(canceled ? CancellationTokenSource.s_canceledSource : null)
- {
- }
-
- /// <summary>
- /// Registers a delegate that will be called when this <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
- /// </summary>
- /// <remarks>
- /// <para>
- /// If this token is already in the canceled state, the
- /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
- /// propagated out of this method call.
- /// </para>
- /// <para>
- /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, will be captured
- /// along with the delegate and will be used when executing it.
- /// </para>
- /// </remarks>
- /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
- /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can
- /// be used to unregister the callback.</returns>
- /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
- public CancellationTokenRegistration Register(Action callback) =>
- Register(
- s_actionToActionObjShunt,
- callback ?? throw new ArgumentNullException(nameof(callback)),
- useSyncContext: false,
- useExecutionContext: true);
-
- /// <summary>
- /// Registers a delegate that will be called when this
- /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
- /// </summary>
- /// <remarks>
- /// <para>
- /// If this token is already in the canceled state, the
- /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
- /// propagated out of this method call.
- /// </para>
- /// <para>
- /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, will be captured
- /// along with the delegate and will be used when executing it.
- /// </para>
- /// </remarks>
- /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
- /// <param name="useSynchronizationContext">A Boolean value that indicates whether to capture
- /// the current <see cref="T:System.Threading.SynchronizationContext">SynchronizationContext</see> and use it
- /// when invoking the <paramref name="callback"/>.</param>
- /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can
- /// be used to unregister the callback.</returns>
- /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
- public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext) =>
- Register(
- s_actionToActionObjShunt,
- callback ?? throw new ArgumentNullException(nameof(callback)),
- useSynchronizationContext,
- useExecutionContext: true);
-
- /// <summary>
- /// Registers a delegate that will be called when this
- /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
- /// </summary>
- /// <remarks>
- /// <para>
- /// If this token is already in the canceled state, the
- /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
- /// propagated out of this method call.
- /// </para>
- /// <para>
- /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, will be captured
- /// along with the delegate and will be used when executing it.
- /// </para>
- /// </remarks>
- /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
- /// <param name="state">The state to pass to the <paramref name="callback"/> when the delegate is invoked. This may be null.</param>
- /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can
- /// be used to unregister the callback.</returns>
- /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
- public CancellationTokenRegistration Register(Action<object> callback, object state) =>
- Register(callback, state, useSyncContext: false, useExecutionContext: true);
-
- /// <summary>
- /// Registers a delegate that will be called when this
- /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
- /// </summary>
- /// <remarks>
- /// <para>
- /// If this token is already in the canceled state, the
- /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
- /// propagated out of this method call.
- /// </para>
- /// <para>
- /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists,
- /// will be captured along with the delegate and will be used when executing it.
- /// </para>
- /// </remarks>
- /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
- /// <param name="state">The state to pass to the <paramref name="callback"/> when the delegate is invoked. This may be null.</param>
- /// <param name="useSynchronizationContext">A Boolean value that indicates whether to capture
- /// the current <see cref="T:System.Threading.SynchronizationContext">SynchronizationContext</see> and use it
- /// when invoking the <paramref name="callback"/>.</param>
- /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can
- /// be used to unregister the callback.</returns>
- /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
- /// <exception cref="T:System.ObjectDisposedException">The associated <see
- /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
- public CancellationTokenRegistration Register(Action<object> callback, object state, bool useSynchronizationContext) =>
- Register(callback, state, useSynchronizationContext, useExecutionContext: true);
-
- // helper for internal registration needs that don't require an EC capture (e.g. creating linked token sources, or registering unstarted TPL tasks)
- // has a handy signature, and skips capturing execution context.
- internal CancellationTokenRegistration InternalRegisterWithoutEC(Action<object> callback, object state) =>
- Register(callback, state, useSyncContext: false, useExecutionContext: false);
-
- // the real work..
- private CancellationTokenRegistration Register(Action<object> callback, object state, bool useSyncContext, bool useExecutionContext)
- {
- if (callback == null)
- {
- throw new ArgumentNullException(nameof(callback));
- }
-
- CancellationTokenSource source = _source;
- return source != null ?
- source.InternalRegister(callback, state, useSyncContext ? SynchronizationContext.Current : null, useExecutionContext ? ExecutionContext.Capture() : null) :
- default; // Nothing to do for tokens than can never reach the canceled state. Give back a dummy registration.
- }
-
- /// <summary>
- /// Determines whether the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance is equal to the
- /// specified token.
- /// </summary>
- /// <param name="other">The other <see cref="T:System.Threading.CancellationToken">CancellationToken</see> to which to compare this
- /// instance.</param>
- /// <returns>True if the instances are equal; otherwise, false. Two tokens are equal if they are associated
- /// with the same <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> or if they were both constructed
- /// from public CancellationToken constructors and their <see cref="IsCancellationRequested"/> values are equal.</returns>
- public bool Equals(CancellationToken other) => _source == other._source;
-
- /// <summary>
- /// Determines whether the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance is equal to the
- /// specified <see cref="T:System.Object"/>.
- /// </summary>
- /// <param name="other">The other object to which to compare this instance.</param>
- /// <returns>True if <paramref name="other"/> is a <see cref="T:System.Threading.CancellationToken">CancellationToken</see>
- /// and if the two instances are equal; otherwise, false. Two tokens are equal if they are associated
- /// with the same <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> or if they were both constructed
- /// from public CancellationToken constructors and their <see cref="IsCancellationRequested"/> values are equal.</returns>
- /// <exception cref="T:System.ObjectDisposedException">An associated <see
- /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
- public override bool Equals(object other) => other is CancellationToken && Equals((CancellationToken)other);
-
- /// <summary>
- /// Serves as a hash function for a <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
- /// </summary>
- /// <returns>A hash code for the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance.</returns>
- public override int GetHashCode() => (_source ?? CancellationTokenSource.s_neverCanceledSource).GetHashCode();
-
- /// <summary>
- /// Determines whether two <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instances are equal.
- /// </summary>
- /// <param name="left">The first instance.</param>
- /// <param name="right">The second instance.</param>
- /// <returns>True if the instances are equal; otherwise, false.</returns>
- /// <exception cref="T:System.ObjectDisposedException">An associated <see
- /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
- public static bool operator ==(CancellationToken left, CancellationToken right) => left.Equals(right);
-
- /// <summary>
- /// Determines whether two <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instances are not equal.
- /// </summary>
- /// <param name="left">The first instance.</param>
- /// <param name="right">The second instance.</param>
- /// <returns>True if the instances are not equal; otherwise, false.</returns>
- /// <exception cref="T:System.ObjectDisposedException">An associated <see
- /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
- public static bool operator !=(CancellationToken left, CancellationToken right) => !left.Equals(right);
-
- /// <summary>
- /// Throws a <see cref="T:System.OperationCanceledException">OperationCanceledException</see> if
- /// this token has had cancellation requested.
- /// </summary>
- /// <remarks>
- /// This method provides functionality equivalent to:
- /// <code>
- /// if (token.IsCancellationRequested)
- /// throw new OperationCanceledException(token);
- /// </code>
- /// </remarks>
- /// <exception cref="System.OperationCanceledException">The token has had cancellation requested.</exception>
- /// <exception cref="T:System.ObjectDisposedException">The associated <see
- /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
- public void ThrowIfCancellationRequested()
- {
- if (IsCancellationRequested)
- {
- ThrowOperationCanceledException();
- }
- }
-
- // Throws an OCE; separated out to enable better inlining of ThrowIfCancellationRequested
- private void ThrowOperationCanceledException() =>
- throw new OperationCanceledException(SR.OperationCanceled, this);
- }
-}
}
}
-
-
/// <summary>
/// Wait for a single callback to complete (or, more specifically, to not be running).
/// It is ok to call this method if the callback has already finished.
{
internal static readonly Action<object> s_linkedTokenCancelDelegate =
s => ((CancellationTokenSource)s).NotifyCancellation(throwOnFirstException: false); // skip ThrowIfDisposed() check in Cancel()
- private CancellationTokenRegistration[] m_linkingRegistrations;
+ private CancellationTokenRegistration[] _linkingRegistrations;
internal LinkedNCancellationTokenSource(params CancellationToken[] tokens)
{
- m_linkingRegistrations = new CancellationTokenRegistration[tokens.Length];
+ _linkingRegistrations = new CancellationTokenRegistration[tokens.Length];
for (int i = 0; i < tokens.Length; i++)
{
if (tokens[i].CanBeCanceled)
{
- m_linkingRegistrations[i] = tokens[i].InternalRegisterWithoutEC(s_linkedTokenCancelDelegate, this);
+ _linkingRegistrations[i] = tokens[i].InternalRegisterWithoutEC(s_linkedTokenCancelDelegate, this);
}
// Empty slots in the array will be default(CancellationTokenRegistration), which are nops to Dispose.
// Based on usage patterns, such occurrences should also be rare, such that it's not worth resizing
return;
}
- CancellationTokenRegistration[] linkingRegistrations = m_linkingRegistrations;
+ CancellationTokenRegistration[] linkingRegistrations = _linkingRegistrations;
if (linkingRegistrations != null)
{
- m_linkingRegistrations = null; // release for GC once we're done enumerating
+ _linkingRegistrations = null; // release for GC once we're done enumerating
for (int i = 0; i < linkingRegistrations.Length; i++)
{
linkingRegistrations[i].Dispose();
+++ /dev/null
-// 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.Runtime.InteropServices;
-using System.Threading.Tasks;
-
-// The class will be part of the current System.Threading namespace
-
-namespace System.Threading
-{
- /// <summary>
- /// Limits the number of threads that can access a resource or pool of resources concurrently.
- /// </summary>
- /// <remarks>
- /// <para>
- /// The <see cref="SemaphoreSlim"/> provides a lightweight semaphore class that doesn't
- /// use Windows kernel semaphores.
- /// </para>
- /// <para>
- /// All public and protected members of <see cref="SemaphoreSlim"/> 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 <see cref="SemaphoreSlim"/> have
- /// completed.
- /// </para>
- /// </remarks>
- [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 int m_waitCount;
-
- /// <summary>
- /// This is used to help prevent waking more waiters than necessary. It's not perfect and sometimes more waiters than
- /// necessary may still be woken, see <see cref="WaitUntilCountOrTimeout"/>.
- /// </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;
-
- // 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<bool> s_trueTask =
- new Task<bool>(false, true, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default);
- // A pre-completed task with Result==false
- private readonly static Task<bool> s_falseTask =
- new Task<bool>(false, false, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default);
-
- // No maximum constant
- private const int NO_MAXIMUM = int.MaxValue;
-
- // Task in a linked list of asynchronous waiters
- private sealed class TaskNode : Task<bool>, 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
-
- /// <summary>
- /// Gets the current count of the <see cref="SemaphoreSlim"/>.
- /// </summary>
- /// <value>The current count of the <see cref="SemaphoreSlim"/>.</value>
- public int CurrentCount
- {
- get { return m_currentCount; }
- }
-
- /// <summary>
- /// Returns a <see cref="T:System.Threading.WaitHandle"/> that can be used to wait on the semaphore.
- /// </summary>
- /// <value>A <see cref="T:System.Threading.WaitHandle"/> that can be used to wait on the
- /// semaphore.</value>
- /// <remarks>
- /// A successful wait on the <see cref="AvailableWaitHandle"/> does not imply a successful wait on
- /// the <see cref="SemaphoreSlim"/> itself, nor does it decrement the semaphore's
- /// count. <see cref="AvailableWaitHandle"/> 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.
- /// </remarks>
- /// <exception cref="T:System.ObjectDisposedException">The <see
- /// cref="SemaphoreSlim"/> has been disposed.</exception>
- 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
- /// <summary>
- /// Initializes a new instance of the <see cref="SemaphoreSlim"/> class, specifying
- /// the initial number of requests that can be granted concurrently.
- /// </summary>
- /// <param name="initialCount">The initial number of requests for the semaphore that can be granted
- /// concurrently.</param>
- /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="initialCount"/>
- /// is less than 0.</exception>
- public SemaphoreSlim(int initialCount)
- : this(initialCount, NO_MAXIMUM)
- {
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="SemaphoreSlim"/> class, specifying
- /// the initial and maximum number of requests that can be granted concurrently.
- /// </summary>
- /// <param name="initialCount">The initial number of requests for the semaphore that can be granted
- /// concurrently.</param>
- /// <param name="maxCount">The maximum number of requests for the semaphore that can be granted
- /// concurrently.</param>
- /// <exception cref="T:System.ArgumentOutOfRangeException"> <paramref name="initialCount"/>
- /// is less than 0. -or-
- /// <paramref name="initialCount"/> is greater than <paramref name="maxCount"/>. -or-
- /// <paramref name="maxCount"/> is less than 0.</exception>
- public SemaphoreSlim(int initialCount, int maxCount)
- {
- if (initialCount < 0 || initialCount > maxCount)
- {
- throw new ArgumentOutOfRangeException(
- nameof(initialCount), initialCount, SR.SemaphoreSlim_ctor_InitialCountWrong);
- }
-
- //validate input
- if (maxCount <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(maxCount), maxCount, SR.SemaphoreSlim_ctor_MaxCountWrong);
- }
-
- m_maxCount = maxCount;
- m_lockObj = new object();
- m_currentCount = initialCount;
- }
-
- #endregion
-
- #region Methods
- /// <summary>
- /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>.
- /// </summary>
- /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
- /// disposed.</exception>
- public void Wait()
- {
- // Call wait with infinite timeout
- Wait(Timeout.Infinite, new CancellationToken());
- }
-
- /// <summary>
- /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, while observing a
- /// <see cref="T:System.Threading.CancellationToken"/>.
- /// </summary>
- /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> token to
- /// observe.</param>
- /// <exception cref="T:System.OperationCanceledException"><paramref name="cancellationToken"/> was
- /// canceled.</exception>
- /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
- /// disposed.</exception>
- public void Wait(CancellationToken cancellationToken)
- {
- // Call wait with infinite timeout
- Wait(Timeout.Infinite, cancellationToken);
- }
-
- /// <summary>
- /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, using a <see
- /// cref="T:System.TimeSpan"/> to measure the time interval.
- /// </summary>
- /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
- /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
- /// </param>
- /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>;
- /// otherwise, false.</returns>
- /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
- /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
- /// than <see cref="System.int.MaxValue"/>.</exception>
- public bool Wait(TimeSpan timeout)
- {
- // Validate the timeout
- long totalMilliseconds = (long)timeout.TotalMilliseconds;
- if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
- {
- throw new System.ArgumentOutOfRangeException(
- nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeoutWrong);
- }
-
- // Call wait with the timeout milliseconds
- return Wait((int)timeout.TotalMilliseconds, new CancellationToken());
- }
-
- /// <summary>
- /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, using a <see
- /// cref="T:System.TimeSpan"/> to measure the time interval, while observing a <see
- /// cref="T:System.Threading.CancellationToken"/>.
- /// </summary>
- /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
- /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
- /// </param>
- /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
- /// observe.</param>
- /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>;
- /// otherwise, false.</returns>
- /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
- /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
- /// than <see cref="System.int.MaxValue"/>.</exception>
- /// <exception cref="System.OperationCanceledException"><paramref name="cancellationToken"/> was canceled.</exception>
- public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
- {
- // Validate the timeout
- long totalMilliseconds = (long)timeout.TotalMilliseconds;
- if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
- {
- throw new System.ArgumentOutOfRangeException(
- nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeoutWrong);
- }
-
- // Call wait with the timeout milliseconds
- return Wait((int)timeout.TotalMilliseconds, cancellationToken);
- }
-
- /// <summary>
- /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, using a 32-bit
- /// signed integer to measure the time interval.
- /// </summary>
- /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
- /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
- /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>;
- /// otherwise, false.</returns>
- /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
- /// negative number other than -1, which represents an infinite time-out.</exception>
- public bool Wait(int millisecondsTimeout)
- {
- return Wait(millisecondsTimeout, new CancellationToken());
- }
-
-
- /// <summary>
- /// Blocks the current thread until it can 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>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>; otherwise, false.</returns>
- /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a negative number other than -1,
- /// which represents an infinite time-out.</exception>
- /// <exception cref="System.OperationCanceledException"><paramref name="cancellationToken"/> was canceled.</exception>
- public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
- {
- CheckDispose();
-
- // Validate input
- if (millisecondsTimeout < -1)
- {
- throw new ArgumentOutOfRangeException(
- nameof(millisecondsTimeout), millisecondsTimeout, SR.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<bool> asyncWaitTask = null;
- bool lockTaken = false;
-
- //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
- CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCanceledEventHandler, this);
- try
- {
- // Perf: first spin wait for the count to be positive.
- // This additional amount of spinwaiting in addition
- // to Monitor.Enter()’s spinwaiting has shown measurable perf gains in test scenarios.
- if (m_currentCount == 0)
- {
- // Monitor.Enter followed by Monitor.Wait is much more expensive than waiting on an event as it involves another
- // spin, contention, etc. The usual number of spin iterations that would otherwise be used here is increased to
- // lessen that extra expense of doing a proper wait.
- int spinCount = SpinWait.SpinCountforSpinBeforeWait * 4;
- const int Sleep1Threshold = SpinWait.Sleep1ThresholdForSpinBeforeWait * 4;
-
- var spinner = new SpinWait();
- while (spinner.Count < spinCount)
- {
- spinner.SpinOnce(Sleep1Threshold);
-
- if (m_currentCount != 0)
- {
- break;
- }
- }
- }
-
- // 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;
- }
-
- /// <summary>
- /// Local helper function, waits on the monitor until the monitor receives signal or the
- /// timeout is expired
- /// </summary>
- /// <param name="millisecondsTimeout">The maximum timeout</param>
- /// <param name="startTime">The start ticks to calculate the elapsed time</param>
- /// <param name="cancellationToken">The CancellationToken to observe.</param>
- /// <returns>true if the monitor received a signal, false if the timeout expired</returns>
- 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 **
- bool waitSuccessful = Monitor.Wait(m_lockObj, 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
- // because it was pulsed. For instance, this thread may have timed out and may have been waiting to reacquire
- // the lock before returning from Monitor.Wait, in which case we don't know whether this thread got pulsed. So
- // in any woken case, decrement the count if possible. As such, timeouts could cause more waiters to wake than
- // necessary.
- if (m_countOfWaitersPulsedToWake != 0)
- {
- --m_countOfWaitersPulsedToWake;
- }
-
- if (!waitSuccessful)
- {
- return false;
- }
- }
-
- return true;
- }
-
- /// <summary>
- /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>.
- /// </summary>
- /// <returns>A task that will complete when the semaphore has been entered.</returns>
- public Task WaitAsync()
- {
- return WaitAsync(Timeout.Infinite, default);
- }
-
- /// <summary>
- /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, while observing a
- /// <see cref="T:System.Threading.CancellationToken"/>.
- /// </summary>
- /// <returns>A task that will complete when the semaphore has been entered.</returns>
- /// <param name="cancellationToken">
- /// The <see cref="T:System.Threading.CancellationToken"/> token to observe.
- /// </param>
- /// <exception cref="T:System.ObjectDisposedException">
- /// The current instance has already been disposed.
- /// </exception>
- public Task WaitAsync(CancellationToken cancellationToken)
- {
- return WaitAsync(Timeout.Infinite, cancellationToken);
- }
-
- /// <summary>
- /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>,
- /// using a 32-bit signed integer to measure the time interval.
- /// </summary>
- /// <param name="millisecondsTimeout">
- /// The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely.
- /// </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)
- {
- return WaitAsync(millisecondsTimeout, default);
- }
-
- /// <summary>
- /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, using a <see
- /// cref="T:System.TimeSpan"/> to measure the time interval, while observing a
- /// <see cref="T:System.Threading.CancellationToken"/>.
- /// </summary>
- /// <param name="timeout">
- /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds
- /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
- /// </param>
- /// <param name="cancellationToken">
- /// The <see cref="T:System.Threading.CancellationToken"/> token 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="T:System.ArgumentOutOfRangeException">
- /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents
- /// an infinite time-out -or- timeout is greater than <see cref="System.int.MaxValue"/>.
- /// </exception>
- public Task<bool> WaitAsync(TimeSpan timeout)
- {
- return WaitAsync(timeout, default);
- }
-
- /// <summary>
- /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, using a <see
- /// cref="T:System.TimeSpan"/> to measure the time interval.
- /// </summary>
- /// <param name="timeout">
- /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds
- /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
- /// </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.ArgumentOutOfRangeException">
- /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents
- /// an infinite time-out -or- timeout is greater than <see cref="System.int.MaxValue"/>.
- /// </exception>
- public Task<bool> WaitAsync(TimeSpan timeout, CancellationToken cancellationToken)
- {
- // Validate the timeout
- long totalMilliseconds = (long)timeout.TotalMilliseconds;
- if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
- {
- throw new System.ArgumentOutOfRangeException(
- nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeoutWrong);
- }
-
- // Call wait with the timeout milliseconds
- return WaitAsync((int)timeout.TotalMilliseconds, cancellationToken);
- }
-
- /// <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.FromCanceled<bool>(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);
- }
- }
- }
-
- /// <summary>Creates a new task and stores it into the async waiters list.</summary>
- /// <returns>The created task.</returns>
- 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;
- }
-
- /// <summary>Removes the waiter task from the linked list.</summary>
- /// <param name="task">The task to remove.</param>
- /// <returns>true if the waiter was in the list; otherwise, false.</returns>
- private bool RemoveAsyncWaiter(TaskNode task)
- {
- Debug.Assert(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;
- }
-
- /// <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(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) :
- 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);
- }
-
- /// <summary>
- /// Exits the <see cref="SemaphoreSlim"/> once.
- /// </summary>
- /// <returns>The previous count of the <see cref="SemaphoreSlim"/>.</returns>
- /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
- /// disposed.</exception>
- public int Release()
- {
- return Release(1);
- }
-
- /// <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;
-
- 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, taking into account how many waiters have previously been pulsed to wake
- // but have not yet woken
- int waitCount = m_waitCount;
- Debug.Assert(m_countOfWaitersPulsedToWake <= waitCount);
- int waitersToNotify = Math.Min(currentCount, waitCount) - m_countOfWaitersPulsedToWake;
- if (waitersToNotify > 0)
- {
- // Ideally, limiting to a maximum of releaseCount would not be necessary and could be an assert instead, but
- // since WaitUntilCountOrTimeout() does not have enough information to tell whether a woken thread was
- // pulsed, it's possible for m_countOfWaitersPulsedToWake to be less than the number of threads that have
- // actually been pulsed to wake.
- if (waitersToNotify > releaseCount)
- {
- waitersToNotify = releaseCount;
- }
-
- m_countOfWaitersPulsedToWake += waitersToNotify;
- 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;
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- private static void QueueWaiterTask(TaskNode waiterTask)
- {
- ThreadPool.UnsafeQueueCustomWorkItem(waiterTask, forceGlobal: false);
- }
-
- /// <summary>
- /// Releases all resources used by the current instance of <see
- /// cref="SemaphoreSlim"/>.
- /// </summary>
- /// <remarks>
- /// Unlike most of the members of <see cref="SemaphoreSlim"/>, <see cref="Dispose()"/> is not
- /// thread-safe and may not be used concurrently with other members of this instance.
- /// </remarks>
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- /// <summary>
- /// When overridden in a derived class, releases the unmanaged resources used by the
- /// <see cref="T:System.Threading.ManualResetEventSlim"/>, and optionally releases the managed resources.
- /// </summary>
- /// <param name="disposing">true to release both managed and unmanaged resources;
- /// false to release only unmanaged resources.</param>
- /// <remarks>
- /// Unlike most of the members of <see cref="SemaphoreSlim"/>, <see cref="Dispose(bool)"/> is not
- /// thread-safe and may not be used concurrently with other members of this instance.
- /// </remarks>
- 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;
- }
- }
-
-
-
- /// <summary>
- /// Private helper method to wake up waiters when a cancellationToken gets canceled.
- /// </summary>
- private static Action<object> s_cancellationTokenCanceledEventHandler = new Action<object>(CancellationTokenCanceledEventHandler);
- private static void CancellationTokenCanceledEventHandler(object obj)
- {
- SemaphoreSlim semaphore = obj as SemaphoreSlim;
- Debug.Assert(semaphore != null, "Expected a SemaphoreSlim");
- lock (semaphore.m_lockObj)
- {
- Monitor.PulseAll(semaphore.m_lockObj); //wake up all waiters.
- }
- }
-
- /// <summary>
- /// Checks the dispose status by checking the lock object, if it is null means that object
- /// has been disposed and throw ObjectDisposedException
- /// </summary>
- private void CheckDispose()
- {
- if (m_lockObj == null)
- {
- throw new ObjectDisposedException(null, SR.SemaphoreSlim_Disposed);
- }
- }
-
- #endregion
- }
-}
internal partial class Kernel32
{
[DllImport(Libraries.Kernel32, SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseHandle(IntPtr handle);
}
}
--- /dev/null
+// 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.
+
+namespace Microsoft.Win32.SafeHandles
+{
+ public sealed partial class SafeWaitHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ protected override bool ReleaseHandle() => Interop.Kernel32.CloseHandle(handle);
+ }
+}
--- /dev/null
+// 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.
+
+using System;
+
+namespace Microsoft.Win32.SafeHandles
+{
+ public sealed partial class SafeWaitHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ // Called by P/Invoke marshaler
+ private SafeWaitHandle() : base(true)
+ {
+ }
+
+ public SafeWaitHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle)
+ {
+ SetHandle(existingHandle);
+ }
+ }
+}
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\CriticalHandleZeroOrMinusOneIsInvalid.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeHandleMinusOneIsInvalid.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeHandleZeroOrMinusOneIsInvalid.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeWaitHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Action.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\AccessViolationException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\ApplicationException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ApartmentState.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\AsyncLocal.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\AutoResetEvent.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\CancellationToken.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\DeferredDisposableLifetime.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\EventResetMode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\EventWaitHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ReaderWriterLockSlim.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Semaphore.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\SemaphoreFullException.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\SemaphoreSlim.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\SendOrPostCallback.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\SpinWait.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\SynchronizationLockException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Interop.BOOL.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Interop.Libraries.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.CancelIoEx.cs" />
- <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.CloseHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FileAttributes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FILE_INFO_BY_HANDLE_CLASS.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FileTypes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.CREATEFILE2_EXTENDED_PARAMETERS.cs" />
</ItemGroup>
<ItemGroup Condition="$(TargetsWindows) or '$(FeaturePal)'=='true'">
+ <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeWaitHandle.Windows.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.CloseHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.Constants.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Interop.Errors.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FormatMessage.cs" />
--- /dev/null
+// 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.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Threading
+{
+ /// <summary>
+ /// Propagates notification that operations should be canceled.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// A <see cref="CancellationToken"/> may be created directly in an unchangeable canceled or non-canceled state
+ /// using the CancellationToken's constructors. However, to have a CancellationToken that can change
+ /// from a non-canceled to a canceled state,
+ /// <see cref="System.Threading.CancellationTokenSource">CancellationTokenSource</see> must be used.
+ /// CancellationTokenSource exposes the associated CancellationToken that may be canceled by the source through its
+ /// <see cref="System.Threading.CancellationTokenSource.Token">Token</see> property.
+ /// </para>
+ /// <para>
+ /// Once canceled, a token may not transition to a non-canceled state, and a token whose
+ /// <see cref="CanBeCanceled"/> is false will never change to one that can be canceled.
+ /// </para>
+ /// <para>
+ /// All members of this struct are thread-safe and may be used concurrently from multiple threads.
+ /// </para>
+ /// </remarks>
+ [DebuggerDisplay("IsCancellationRequested = {IsCancellationRequested}")]
+ public readonly struct CancellationToken
+ {
+ // The backing TokenSource.
+ // if null, it implicitly represents the same thing as new CancellationToken(false).
+ // When required, it will be instantiated to reflect this.
+ private readonly CancellationTokenSource _source;
+ //!! warning. If more fields are added, the assumptions in CreateLinkedToken may no longer be valid
+
+ private readonly static Action<object> s_actionToActionObjShunt = obj => ((Action)obj)();
+
+ /// <summary>
+ /// Returns an empty CancellationToken value.
+ /// </summary>
+ /// <remarks>
+ /// The <see cref="CancellationToken"/> value returned by this property will be non-cancelable by default.
+ /// </remarks>
+ public static CancellationToken None => default;
+
+ /// <summary>
+ /// Gets whether cancellation has been requested for this token.
+ /// </summary>
+ /// <value>Whether cancellation has been requested for this token.</value>
+ /// <remarks>
+ /// <para>
+ /// This property indicates whether cancellation has been requested for this token,
+ /// either through the token initially being constructed in a canceled state, or through
+ /// calling <see cref="System.Threading.CancellationTokenSource.Cancel()">Cancel</see>
+ /// on the token's associated <see cref="CancellationTokenSource"/>.
+ /// </para>
+ /// <para>
+ /// If this property is true, it only guarantees that cancellation has been requested.
+ /// It does not guarantee that every registered handler
+ /// has finished executing, nor that cancellation requests have finished propagating
+ /// to all registered handlers. Additional synchronization may be required,
+ /// particularly in situations where related objects are being canceled concurrently.
+ /// </para>
+ /// </remarks>
+ public bool IsCancellationRequested => _source != null && _source.IsCancellationRequested;
+
+ /// <summary>
+ /// Gets whether this token is capable of being in the canceled state.
+ /// </summary>
+ /// <remarks>
+ /// If CanBeCanceled returns false, it is guaranteed that the token will never transition
+ /// into a canceled state, meaning that <see cref="IsCancellationRequested"/> will never
+ /// return true.
+ /// </remarks>
+ public bool CanBeCanceled => _source != null;
+
+ /// <summary>
+ /// Gets a <see cref="T:System.Threading.WaitHandle"/> that is signaled when the token is canceled.</summary>
+ /// <remarks>
+ /// Accessing this property causes a <see cref="T:System.Threading.WaitHandle">WaitHandle</see>
+ /// to be instantiated. It is preferable to only use this property when necessary, and to then
+ /// dispose the associated <see cref="CancellationTokenSource"/> instance at the earliest opportunity (disposing
+ /// the source will dispose of this allocated handle). The handle should not be closed or disposed directly.
+ /// </remarks>
+ /// <exception cref="T:System.ObjectDisposedException">The associated <see
+ /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
+ public WaitHandle WaitHandle => (_source ?? CancellationTokenSource.s_neverCanceledSource).WaitHandle;
+
+ // public CancellationToken()
+ // this constructor is implicit for structs
+ // -> this should behaves exactly as for new CancellationToken(false)
+
+ /// <summary>
+ /// Internal constructor only a CancellationTokenSource should create a CancellationToken
+ /// </summary>
+ internal CancellationToken(CancellationTokenSource source) => _source = source;
+
+ /// <summary>
+ /// Initializes the <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
+ /// </summary>
+ /// <param name="canceled">
+ /// The canceled state for the token.
+ /// </param>
+ /// <remarks>
+ /// Tokens created with this constructor will remain in the canceled state specified
+ /// by the <paramref name="canceled"/> parameter. If <paramref name="canceled"/> is false,
+ /// both <see cref="CanBeCanceled"/> and <see cref="IsCancellationRequested"/> will be false.
+ /// If <paramref name="canceled"/> is true,
+ /// both <see cref="CanBeCanceled"/> and <see cref="IsCancellationRequested"/> will be true.
+ /// </remarks>
+ public CancellationToken(bool canceled) : this(canceled ? CancellationTokenSource.s_canceledSource : null)
+ {
+ }
+
+ /// <summary>
+ /// Registers a delegate that will be called when this <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// If this token is already in the canceled state, the
+ /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
+ /// propagated out of this method call.
+ /// </para>
+ /// <para>
+ /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, will be captured
+ /// along with the delegate and will be used when executing it.
+ /// </para>
+ /// </remarks>
+ /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
+ /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can
+ /// be used to unregister the callback.</returns>
+ /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
+ public CancellationTokenRegistration Register(Action callback) =>
+ Register(
+ s_actionToActionObjShunt,
+ callback ?? throw new ArgumentNullException(nameof(callback)),
+ useSynchronizationContext: false,
+ useExecutionContext: true);
+
+ /// <summary>
+ /// Registers a delegate that will be called when this
+ /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// If this token is already in the canceled state, the
+ /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
+ /// propagated out of this method call.
+ /// </para>
+ /// <para>
+ /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, will be captured
+ /// along with the delegate and will be used when executing it.
+ /// </para>
+ /// </remarks>
+ /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
+ /// <param name="useSynchronizationContext">A Boolean value that indicates whether to capture
+ /// the current <see cref="T:System.Threading.SynchronizationContext">SynchronizationContext</see> and use it
+ /// when invoking the <paramref name="callback"/>.</param>
+ /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can
+ /// be used to unregister the callback.</returns>
+ /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
+ public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext) =>
+ Register(
+ s_actionToActionObjShunt,
+ callback ?? throw new ArgumentNullException(nameof(callback)),
+ useSynchronizationContext,
+ useExecutionContext: true);
+
+ /// <summary>
+ /// Registers a delegate that will be called when this
+ /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// If this token is already in the canceled state, the
+ /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
+ /// propagated out of this method call.
+ /// </para>
+ /// <para>
+ /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, will be captured
+ /// along with the delegate and will be used when executing it.
+ /// </para>
+ /// </remarks>
+ /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
+ /// <param name="state">The state to pass to the <paramref name="callback"/> when the delegate is invoked. This may be null.</param>
+ /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can
+ /// be used to unregister the callback.</returns>
+ /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
+ public CancellationTokenRegistration Register(Action<object> callback, object state) =>
+ Register(callback, state, useSynchronizationContext: false, useExecutionContext: true);
+
+ /// <summary>
+ /// Registers a delegate that will be called when this
+ /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// If this token is already in the canceled state, the
+ /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
+ /// propagated out of this method call.
+ /// </para>
+ /// <para>
+ /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists,
+ /// will be captured along with the delegate and will be used when executing it.
+ /// </para>
+ /// </remarks>
+ /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
+ /// <param name="state">The state to pass to the <paramref name="callback"/> when the delegate is invoked. This may be null.</param>
+ /// <param name="useSynchronizationContext">A Boolean value that indicates whether to capture
+ /// the current <see cref="T:System.Threading.SynchronizationContext">SynchronizationContext</see> and use it
+ /// when invoking the <paramref name="callback"/>.</param>
+ /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can
+ /// be used to unregister the callback.</returns>
+ /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
+ /// <exception cref="T:System.ObjectDisposedException">The associated <see
+ /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
+ public CancellationTokenRegistration Register(Action<object> callback, object state, bool useSynchronizationContext) =>
+ Register(callback, state, useSynchronizationContext, useExecutionContext: true);
+
+ // helper for internal registration needs that don't require an EC capture (e.g. creating linked token sources, or registering unstarted TPL tasks)
+ // has a handy signature, and skips capturing execution context.
+ internal CancellationTokenRegistration InternalRegisterWithoutEC(Action<object> callback, object state) =>
+ Register(callback, state, useSynchronizationContext: false, useExecutionContext: false);
+
+ /// <summary>
+ /// Registers a delegate that will be called when this
+ /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// If this token is already in the canceled state, the
+ /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
+ /// propagated out of this method call.
+ /// </para>
+ /// </remarks>
+ /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
+ /// <param name="state">The state to pass to the <paramref name="callback"/> when the delegate is invoked. This may be null.</param>
+ /// <param name="useSynchronizationContext">A Boolean value that indicates whether to capture
+ /// the current <see cref="T:System.Threading.SynchronizationContext">SynchronizationContext</see> and use it
+ /// when invoking the <paramref name="callback"/>.</param>
+ /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can
+ /// be used to unregister the callback.</returns>
+ /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
+ /// <exception cref="T:System.ObjectDisposedException">The associated <see
+ /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
+#if CORECLR
+ private
+#else
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public
+#endif
+ CancellationTokenRegistration Register(Action<object> callback, object state, bool useSynchronizationContext, bool useExecutionContext)
+ {
+ if (callback == null)
+ throw new ArgumentNullException(nameof(callback));
+
+ CancellationTokenSource source = _source;
+ return source != null ?
+ source.InternalRegister(callback, state, useSynchronizationContext ? SynchronizationContext.Current : null, useExecutionContext ? ExecutionContext.Capture() : null) :
+ default; // Nothing to do for tokens than can never reach the canceled state. Give back a dummy registration.
+ }
+
+ /// <summary>
+ /// Determines whether the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance is equal to the
+ /// specified token.
+ /// </summary>
+ /// <param name="other">The other <see cref="T:System.Threading.CancellationToken">CancellationToken</see> to which to compare this
+ /// instance.</param>
+ /// <returns>True if the instances are equal; otherwise, false. Two tokens are equal if they are associated
+ /// with the same <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> or if they were both constructed
+ /// from public CancellationToken constructors and their <see cref="IsCancellationRequested"/> values are equal.</returns>
+ public bool Equals(CancellationToken other) => _source == other._source;
+
+ /// <summary>
+ /// Determines whether the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance is equal to the
+ /// specified <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="other">The other object to which to compare this instance.</param>
+ /// <returns>True if <paramref name="other"/> is a <see cref="T:System.Threading.CancellationToken">CancellationToken</see>
+ /// and if the two instances are equal; otherwise, false. Two tokens are equal if they are associated
+ /// with the same <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> or if they were both constructed
+ /// from public CancellationToken constructors and their <see cref="IsCancellationRequested"/> values are equal.</returns>
+ /// <exception cref="T:System.ObjectDisposedException">An associated <see
+ /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
+ public override bool Equals(object other) => other is CancellationToken && Equals((CancellationToken)other);
+
+ /// <summary>
+ /// Serves as a hash function for a <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
+ /// </summary>
+ /// <returns>A hash code for the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance.</returns>
+ public override int GetHashCode() => (_source ?? CancellationTokenSource.s_neverCanceledSource).GetHashCode();
+
+ /// <summary>
+ /// Determines whether two <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instances are equal.
+ /// </summary>
+ /// <param name="left">The first instance.</param>
+ /// <param name="right">The second instance.</param>
+ /// <returns>True if the instances are equal; otherwise, false.</returns>
+ /// <exception cref="T:System.ObjectDisposedException">An associated <see
+ /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
+ public static bool operator ==(CancellationToken left, CancellationToken right) => left.Equals(right);
+
+ /// <summary>
+ /// Determines whether two <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instances are not equal.
+ /// </summary>
+ /// <param name="left">The first instance.</param>
+ /// <param name="right">The second instance.</param>
+ /// <returns>True if the instances are not equal; otherwise, false.</returns>
+ /// <exception cref="T:System.ObjectDisposedException">An associated <see
+ /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
+ public static bool operator !=(CancellationToken left, CancellationToken right) => !left.Equals(right);
+
+ /// <summary>
+ /// Throws a <see cref="T:System.OperationCanceledException">OperationCanceledException</see> if
+ /// this token has had cancellation requested.
+ /// </summary>
+ /// <remarks>
+ /// This method provides functionality equivalent to:
+ /// <code>
+ /// if (token.IsCancellationRequested)
+ /// throw new OperationCanceledException(token);
+ /// </code>
+ /// </remarks>
+ /// <exception cref="System.OperationCanceledException">The token has had cancellation requested.</exception>
+ /// <exception cref="T:System.ObjectDisposedException">The associated <see
+ /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
+ public void ThrowIfCancellationRequested()
+ {
+ if (IsCancellationRequested)
+ ThrowOperationCanceledException();
+ }
+
+ // Throws an OCE; separated out to enable better inlining of ThrowIfCancellationRequested
+ private void ThrowOperationCanceledException() =>
+ throw new OperationCanceledException(SR.OperationCanceled, this);
+ }
+}
--- /dev/null
+// 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.
+
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+
+namespace System.Threading
+{
+ /// <summary>
+ /// Limits the number of threads that can access a resource or pool of resources concurrently.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// The <see cref="SemaphoreSlim"/> provides a lightweight semaphore class that doesn't
+ /// use Windows kernel semaphores.
+ /// </para>
+ /// <para>
+ /// All public and protected members of <see cref="SemaphoreSlim"/> 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 <see cref="SemaphoreSlim"/> have
+ /// completed.
+ /// </para>
+ /// </remarks>
+ [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 int m_waitCount;
+
+ /// <summary>
+ /// This is used to help prevent waking more waiters than necessary. It's not perfect and sometimes more waiters than
+ /// necessary may still be woken, see <see cref="WaitUntilCountOrTimeout"/>.
+ /// </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;
+
+ // 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 static readonly Task<bool> s_trueTask =
+ new Task<bool>(false, true, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default);
+ // A pre-completed task with Result==false
+ private readonly static Task<bool> s_falseTask =
+ new Task<bool>(false, false, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default);
+
+ // No maximum constant
+ private const int NO_MAXIMUM = int.MaxValue;
+
+ // Task in a linked list of asynchronous waiters
+ private sealed class TaskNode : Task<bool>, 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");
+ }
+#if CORECLR
+ void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { /* nop */ }
+#endif
+ }
+ #endregion
+
+ #region Public properties
+
+ /// <summary>
+ /// Gets the current count of the <see cref="SemaphoreSlim"/>.
+ /// </summary>
+ /// <value>The current count of the <see cref="SemaphoreSlim"/>.</value>
+ public int CurrentCount
+ {
+ get { return m_currentCount; }
+ }
+
+ /// <summary>
+ /// Returns a <see cref="T:System.Threading.WaitHandle"/> that can be used to wait on the semaphore.
+ /// </summary>
+ /// <value>A <see cref="T:System.Threading.WaitHandle"/> that can be used to wait on the
+ /// semaphore.</value>
+ /// <remarks>
+ /// A successful wait on the <see cref="AvailableWaitHandle"/> does not imply a successful wait on
+ /// the <see cref="SemaphoreSlim"/> itself, nor does it decrement the semaphore's
+ /// count. <see cref="AvailableWaitHandle"/> 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.
+ /// </remarks>
+ /// <exception cref="T:System.ObjectDisposedException">The <see
+ /// cref="SemaphoreSlim"/> has been disposed.</exception>
+ 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
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SemaphoreSlim"/> class, specifying
+ /// the initial number of requests that can be granted concurrently.
+ /// </summary>
+ /// <param name="initialCount">The initial number of requests for the semaphore that can be granted
+ /// concurrently.</param>
+ /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="initialCount"/>
+ /// is less than 0.</exception>
+ public SemaphoreSlim(int initialCount)
+ : this(initialCount, NO_MAXIMUM)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SemaphoreSlim"/> class, specifying
+ /// the initial and maximum number of requests that can be granted concurrently.
+ /// </summary>
+ /// <param name="initialCount">The initial number of requests for the semaphore that can be granted
+ /// concurrently.</param>
+ /// <param name="maxCount">The maximum number of requests for the semaphore that can be granted
+ /// concurrently.</param>
+ /// <exception cref="T:System.ArgumentOutOfRangeException"> <paramref name="initialCount"/>
+ /// is less than 0. -or-
+ /// <paramref name="initialCount"/> is greater than <paramref name="maxCount"/>. -or-
+ /// <paramref name="maxCount"/> is less than 0.</exception>
+ public SemaphoreSlim(int initialCount, int maxCount)
+ {
+ if (initialCount < 0 || initialCount > maxCount)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(initialCount), initialCount, SR.SemaphoreSlim_ctor_InitialCountWrong);
+ }
+
+ //validate input
+ if (maxCount <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(maxCount), maxCount, SR.SemaphoreSlim_ctor_MaxCountWrong);
+ }
+
+ m_maxCount = maxCount;
+ m_lockObj = new object();
+ m_currentCount = initialCount;
+ }
+
+ #endregion
+
+ #region Methods
+ /// <summary>
+ /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>.
+ /// </summary>
+ /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
+ /// disposed.</exception>
+ public void Wait()
+ {
+ // Call wait with infinite timeout
+ Wait(Timeout.Infinite, new CancellationToken());
+ }
+
+ /// <summary>
+ /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, while observing a
+ /// <see cref="T:System.Threading.CancellationToken"/>.
+ /// </summary>
+ /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> token to
+ /// observe.</param>
+ /// <exception cref="T:System.OperationCanceledException"><paramref name="cancellationToken"/> was
+ /// canceled.</exception>
+ /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
+ /// disposed.</exception>
+ public void Wait(CancellationToken cancellationToken)
+ {
+ // Call wait with infinite timeout
+ Wait(Timeout.Infinite, cancellationToken);
+ }
+
+ /// <summary>
+ /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, using a <see
+ /// cref="T:System.TimeSpan"/> to measure the time interval.
+ /// </summary>
+ /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
+ /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
+ /// </param>
+ /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>;
+ /// otherwise, false.</returns>
+ /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
+ /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
+ /// than <see cref="System.Int32.MaxValue"/>.</exception>
+ public bool Wait(TimeSpan timeout)
+ {
+ // Validate the timeout
+ long totalMilliseconds = (long)timeout.TotalMilliseconds;
+ if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
+ {
+ throw new System.ArgumentOutOfRangeException(
+ nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeoutWrong);
+ }
+
+ // Call wait with the timeout milliseconds
+ return Wait((int)timeout.TotalMilliseconds, new CancellationToken());
+ }
+
+ /// <summary>
+ /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, using a <see
+ /// cref="T:System.TimeSpan"/> to measure the time interval, while observing a <see
+ /// cref="T:System.Threading.CancellationToken"/>.
+ /// </summary>
+ /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
+ /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
+ /// </param>
+ /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
+ /// observe.</param>
+ /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>;
+ /// otherwise, false.</returns>
+ /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
+ /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
+ /// than <see cref="System.Int32.MaxValue"/>.</exception>
+ /// <exception cref="System.OperationCanceledException"><paramref name="cancellationToken"/> was canceled.</exception>
+ public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
+ {
+ // Validate the timeout
+ long totalMilliseconds = (long)timeout.TotalMilliseconds;
+ if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
+ {
+ throw new System.ArgumentOutOfRangeException(
+ nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeoutWrong);
+ }
+
+ // Call wait with the timeout milliseconds
+ return Wait((int)timeout.TotalMilliseconds, cancellationToken);
+ }
+
+ /// <summary>
+ /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, using a 32-bit
+ /// signed integer to measure the time interval.
+ /// </summary>
+ /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
+ /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
+ /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>;
+ /// otherwise, false.</returns>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
+ /// negative number other than -1, which represents an infinite time-out.</exception>
+ public bool Wait(int millisecondsTimeout)
+ {
+ return Wait(millisecondsTimeout, new CancellationToken());
+ }
+
+
+ /// <summary>
+ /// Blocks the current thread until it can 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>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>; otherwise, false.</returns>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a negative number other than -1,
+ /// which represents an infinite time-out.</exception>
+ /// <exception cref="System.OperationCanceledException"><paramref name="cancellationToken"/> was canceled.</exception>
+ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
+ {
+ CheckDispose();
+
+ // Validate input
+ if (millisecondsTimeout < -1)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(millisecondsTimeout), millisecondsTimeout, SR.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<bool> asyncWaitTask = null;
+ bool lockTaken = false;
+
+ //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
+ CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCanceledEventHandler, this);
+ try
+ {
+ // Perf: first spin wait for the count to be positive.
+ // This additional amount of spinwaiting in addition
+ // to Monitor.Enter()’s spinwaiting has shown measurable perf gains in test scenarios.
+ if (m_currentCount == 0)
+ {
+ // Monitor.Enter followed by Monitor.Wait is much more expensive than waiting on an event as it involves another
+ // spin, contention, etc. The usual number of spin iterations that would otherwise be used here is increased to
+ // lessen that extra expense of doing a proper wait.
+ int spinCount = SpinWait.SpinCountforSpinBeforeWait * 4;
+ const int Sleep1Threshold = SpinWait.Sleep1ThresholdForSpinBeforeWait * 4;
+
+ var spinner = new SpinWait();
+ while (spinner.Count < spinCount)
+ {
+ spinner.SpinOnce(Sleep1Threshold);
+
+ if (m_currentCount != 0)
+ {
+ break;
+ }
+ }
+ }
+ // 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;
+ }
+
+ /// <summary>
+ /// Local helper function, waits on the monitor until the monitor receives signal or the
+ /// timeout is expired
+ /// </summary>
+ /// <param name="millisecondsTimeout">The maximum timeout</param>
+ /// <param name="startTime">The start ticks to calculate the elapsed time</param>
+ /// <param name="cancellationToken">The CancellationToken to observe.</param>
+ /// <returns>true if the monitor received a signal, false if the timeout expired</returns>
+ 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 **
+ bool waitSuccessful = Monitor.Wait(m_lockObj, 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
+ // because it was pulsed. For instance, this thread may have timed out and may have been waiting to reacquire
+ // the lock before returning from Monitor.Wait, in which case we don't know whether this thread got pulsed. So
+ // in any woken case, decrement the count if possible. As such, timeouts could cause more waiters to wake than
+ // necessary.
+ if (m_countOfWaitersPulsedToWake != 0)
+ {
+ --m_countOfWaitersPulsedToWake;
+ }
+
+ if (!waitSuccessful)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>.
+ /// </summary>
+ /// <returns>A task that will complete when the semaphore has been entered.</returns>
+ public Task WaitAsync()
+ {
+ return WaitAsync(Timeout.Infinite, default);
+ }
+
+ /// <summary>
+ /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, while observing a
+ /// <see cref="T:System.Threading.CancellationToken"/>.
+ /// </summary>
+ /// <returns>A task that will complete when the semaphore has been entered.</returns>
+ /// <param name="cancellationToken">
+ /// The <see cref="T:System.Threading.CancellationToken"/> token to observe.
+ /// </param>
+ /// <exception cref="T:System.ObjectDisposedException">
+ /// The current instance has already been disposed.
+ /// </exception>
+ public Task WaitAsync(CancellationToken cancellationToken)
+ {
+ return WaitAsync(Timeout.Infinite, cancellationToken);
+ }
+
+ /// <summary>
+ /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>,
+ /// using a 32-bit signed integer to measure the time interval.
+ /// </summary>
+ /// <param name="millisecondsTimeout">
+ /// The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely.
+ /// </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)
+ {
+ return WaitAsync(millisecondsTimeout, default);
+ }
+
+ /// <summary>
+ /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, using a <see
+ /// cref="T:System.TimeSpan"/> to measure the time interval, while observing a
+ /// <see cref="T:System.Threading.CancellationToken"/>.
+ /// </summary>
+ /// <param name="timeout">
+ /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds
+ /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
+ /// </param>
+ /// <param name="cancellationToken">
+ /// The <see cref="T:System.Threading.CancellationToken"/> token 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="T:System.ArgumentOutOfRangeException">
+ /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents
+ /// an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>.
+ /// </exception>
+ public Task<bool> WaitAsync(TimeSpan timeout)
+ {
+ return WaitAsync(timeout, default);
+ }
+
+ /// <summary>
+ /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, using a <see
+ /// cref="T:System.TimeSpan"/> to measure the time interval.
+ /// </summary>
+ /// <param name="timeout">
+ /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds
+ /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
+ /// </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.ArgumentOutOfRangeException">
+ /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents
+ /// an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>.
+ /// </exception>
+ public Task<bool> WaitAsync(TimeSpan timeout, CancellationToken cancellationToken)
+ {
+ // Validate the timeout
+ long totalMilliseconds = (long)timeout.TotalMilliseconds;
+ if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
+ {
+ throw new System.ArgumentOutOfRangeException(
+ nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeoutWrong);
+ }
+
+ // Call wait with the timeout milliseconds
+ return WaitAsync((int)timeout.TotalMilliseconds, cancellationToken);
+ }
+
+ /// <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.FromCanceled<bool>(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);
+ }
+ }
+ }
+
+ /// <summary>Creates a new task and stores it into the async waiters list.</summary>
+ /// <returns>The created task.</returns>
+ 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;
+ }
+
+ /// <summary>Removes the waiter task from the linked list.</summary>
+ /// <param name="task">The task to remove.</param>
+ /// <returns>true if the waiter was in the list; otherwise, false.</returns>
+ private bool RemoveAsyncWaiter(TaskNode task)
+ {
+ Debug.Assert(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;
+ }
+
+ /// <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(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) :
+ 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);
+ }
+
+ /// <summary>
+ /// Exits the <see cref="SemaphoreSlim"/> once.
+ /// </summary>
+ /// <returns>The previous count of the <see cref="SemaphoreSlim"/>.</returns>
+ /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
+ /// disposed.</exception>
+ public int Release()
+ {
+ return Release(1);
+ }
+
+ /// <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;
+
+ 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, taking into account how many waiters have previously been pulsed to wake
+ // but have not yet woken
+ int waitCount = m_waitCount;
+ Debug.Assert(m_countOfWaitersPulsedToWake <= waitCount);
+ int waitersToNotify = Math.Min(currentCount, waitCount) - m_countOfWaitersPulsedToWake;
+ if (waitersToNotify > 0)
+ {
+ // Ideally, limiting to a maximum of releaseCount would not be necessary and could be an assert instead, but
+ // since WaitUntilCountOrTimeout() does not have enough information to tell whether a woken thread was
+ // pulsed, it's possible for m_countOfWaitersPulsedToWake to be less than the number of threads that have
+ // actually been pulsed to wake.
+ if (waitersToNotify > releaseCount)
+ {
+ waitersToNotify = releaseCount;
+ }
+
+ m_countOfWaitersPulsedToWake += waitersToNotify;
+ 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;
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ private static void QueueWaiterTask(TaskNode waiterTask)
+ {
+ ThreadPool.UnsafeQueueCustomWorkItem(waiterTask, forceGlobal: false);
+ }
+
+ /// <summary>
+ /// Releases all resources used by the current instance of <see
+ /// cref="SemaphoreSlim"/>.
+ /// </summary>
+ /// <remarks>
+ /// Unlike most of the members of <see cref="SemaphoreSlim"/>, <see cref="Dispose()"/> is not
+ /// thread-safe and may not be used concurrently with other members of this instance.
+ /// </remarks>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// When overridden in a derived class, releases the unmanaged resources used by the
+ /// <see cref="T:System.Threading.ManualResetEventSlim"/>, and optionally releases the managed resources.
+ /// </summary>
+ /// <param name="disposing">true to release both managed and unmanaged resources;
+ /// false to release only unmanaged resources.</param>
+ /// <remarks>
+ /// Unlike most of the members of <see cref="SemaphoreSlim"/>, <see cref="Dispose(bool)"/> is not
+ /// thread-safe and may not be used concurrently with other members of this instance.
+ /// </remarks>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (m_waitHandle != null)
+ {
+ m_waitHandle.Dispose();
+ m_waitHandle = null;
+ }
+ m_lockObj = null;
+ m_asyncHead = null;
+ m_asyncTail = null;
+ }
+ }
+
+ /// <summary>
+ /// Private helper method to wake up waiters when a cancellationToken gets canceled.
+ /// </summary>
+ private static Action<object> s_cancellationTokenCanceledEventHandler = new Action<object>(CancellationTokenCanceledEventHandler);
+ private static void CancellationTokenCanceledEventHandler(object obj)
+ {
+ SemaphoreSlim semaphore = obj as SemaphoreSlim;
+ Debug.Assert(semaphore != null, "Expected a SemaphoreSlim");
+ lock (semaphore.m_lockObj)
+ {
+ Monitor.PulseAll(semaphore.m_lockObj); //wake up all waiters.
+ }
+ }
+
+ /// <summary>
+ /// Checks the dispose status by checking the lock object, if it is null means that object
+ /// has been disposed and throw ObjectDisposedException
+ /// </summary>
+ private void CheckDispose()
+ {
+ if (m_lockObj == null)
+ {
+ throw new ObjectDisposedException(null, SR.SemaphoreSlim_Disposed);
+ }
+ }
+ #endregion
+ }
+}