Table of Contents

Class ReentrantSemaphore

Namespace
Microsoft.VisualStudio.Threading
Assembly
Microsoft.VisualStudio.Threading.dll

A JoinableTaskFactory-aware semaphore that allows reentrancy without consuming another slot in the semaphore.

public abstract class ReentrantSemaphore : IDisposable
Inheritance
ReentrantSemaphore
Implements
Inherited Members

Properties

CurrentCount

Gets the number of openings that remain in the semaphore.

public int CurrentCount { get; }

Property Value

int

Methods

Create(int, JoinableTaskContext?, ReentrancyMode)

Initializes a new instance of the ReentrantSemaphore class.

public static ReentrantSemaphore Create(int initialCount = 1, JoinableTaskContext? joinableTaskContext = null, ReentrantSemaphore.ReentrancyMode mode = ReentrancyMode.NotAllowed)

Parameters

initialCount int

The initial number of concurrent operations to allow.

joinableTaskContext JoinableTaskContext

The JoinableTaskContext to use to mitigate deadlocks.

mode ReentrantSemaphore.ReentrancyMode

How to respond to a semaphore request by a caller that has already entered the semaphore.

Returns

ReentrantSemaphore

Dispose()

Faults all pending semaphore waiters with ObjectDisposedException and rejects all subsequent attempts to enter the semaphore with the same exception.

public void Dispose()

Dispose(bool)

Disposes managed and unmanaged resources held by this instance.

protected virtual void Dispose(bool disposing)

Parameters

disposing bool

true if Dispose() was called; false if the object is being finalized.

ExecuteAsync(Func<Task>, CancellationToken)

Executes a given operation within the semaphore.

public abstract Task ExecuteAsync(Func<Task> operation, CancellationToken cancellationToken = default)

Parameters

operation Func<Task>

The delegate to invoke once the semaphore is entered. If a JoinableTaskContext was supplied to the constructor, this delegate will execute on the main thread if this is invoked on the main thread, otherwise it will be invoked on the threadpool. When no JoinableTaskContext is supplied to the constructor, this delegate will execute on the caller's context.

cancellationToken CancellationToken

A cancellation token.

Returns

Task

A task that completes with the result of operation, after the semaphore has been exited.

Exceptions

InvalidOperationException

Thrown when reentrancy is detected and not allowed based due to NotAllowed being provided to the constructor. This happens when code that already holds the semaphore calls code that attempts to again enter the semaphore. When the called code is not awaited on by the caller, it may be appropriate to suppress this reentrancy detection for the method that is called in a fire-and-forget fashion. To suppress this exception for that specific case while preserving overall protection, use SuppressRelevance().

ExecuteAsync<T>(Func<ValueTask<T>>, CancellationToken)

Executes a given operation within the semaphore.

public abstract ValueTask<T> ExecuteAsync<T>(Func<ValueTask<T>> operation, CancellationToken cancellationToken = default)

Parameters

operation Func<ValueTask<T>>

The delegate to invoke once the semaphore is entered. If a JoinableTaskContext was supplied to the constructor, this delegate will execute on the main thread if this is invoked on the main thread, otherwise it will be invoked on the threadpool. When no JoinableTaskContext is supplied to the constructor, this delegate will execute on the caller's context.

cancellationToken CancellationToken

A cancellation token.

Returns

ValueTask<T>

A task that completes with the result of operation, after the semaphore has been exited.

Type Parameters

T

The type of value returned by the operation.

Exceptions

InvalidOperationException

Thrown when reentrancy is detected and not allowed based due to NotAllowed being provided to the constructor. This happens when code that already holds the semaphore calls code that attempts to again enter the semaphore. When the called code is not awaited on by the caller, it may be appropriate to suppress this reentrancy detection for the method that is called in a fire-and-forget fashion. To suppress this exception for that specific case while preserving overall protection, use SuppressRelevance().

SuppressRelevance()

Conceals evidence that the caller has entered this ReentrantSemaphore till its result is disposed.

public virtual ReentrantSemaphore.RevertRelevance SuppressRelevance()

Returns

ReentrantSemaphore.RevertRelevance

A value to dispose to restore visibility of any presence in this semaphore.

Examples

The following snippet demonstrates a way to use this method.

public async Task DoSomethingAsync()
{
    await this.semaphore.ExecuteAsync(async delegate
    {
        // field access under the semaphore
        // ...
        await Task.Yield(); // represents some async work

        // Fire and forget code that uses the semaphore, but should *not*
        // inherit our own posession of the semaphore.
        using (this.semaphore.SuppressRelevance())
        {
            this.DoSomethingLaterAsync().Forget(); // Don't await this, or a deadlock will occur.
        }
    });
}

private async Task DoSomethingLaterAsync()
{
    // This semaphore use will not be seen as nested because of our caller's wrapping
    // the call in SuppressRelevance.
    // So instead of throwing, it will block till its caller releases the semaphore.
    await this.semaphore.ExecuteAsync(async delegate
    {
        // Whatever
        await Task.Yield(); // represents some async work
    });
}

Remarks

This method is useful when the caller is about to spin off another operation (e.g. scheduling work to the threadpool) that it does not consider vital to its own completion, in order to prevent the spun off work from abusing the caller's right to the semaphore.

This is a safe call to make whether or not the semaphore is currently held, or whether reentrancy is allowed on this instance.

ThrowIfFaulted()

Throws an exception if this instance has been faulted.

protected virtual void ThrowIfFaulted()