Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 98 additions & 10 deletions src/Microsoft.VisualStudio.Threading/ReentrantSemaphore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ public static ReentrantSemaphore Create(int initialCount = 1, JoinableTaskContex
/// </param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A task that completes with the result of <paramref name="operation"/>, after the semaphore has been exited.</returns>
public abstract Task ExecuteAsync(Func<Task> operation, CancellationToken cancellationToken = default);
public Task ExecuteAsync(Func<Task> operation, CancellationToken cancellationToken = default)
=> this.ExecuteAsync(operation, null, cancellationToken);

/// <summary>
/// Executes a given operation within the semaphore.
Expand All @@ -167,7 +168,37 @@ public static ReentrantSemaphore Create(int initialCount = 1, JoinableTaskContex
/// </param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A task that completes with the result of <paramref name="operation"/>, after the semaphore has been exited.</returns>
public abstract ValueTask<T> ExecuteAsync<T>(Func<ValueTask<T>> operation, CancellationToken cancellationToken = default);
public ValueTask<T> ExecuteAsync<T>(Func<ValueTask<T>> operation, CancellationToken cancellationToken = default)
=> this.ExecuteValueTaskAsync<T>(operation, null, cancellationToken);

/// <summary>
/// Executes a given operation within the semaphore.
/// </summary>
/// <param name="operation">
/// The delegate to invoke once the semaphore is entered. If a <see cref="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 <see cref="JoinableTaskContext"/> is supplied to the constructor, this delegate will execute on the
/// caller's context.
/// </param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A task that completes with the result of <paramref name="operation"/>, after the semaphore has been exited.</returns>
public Task ExecuteAsync(Func<object?, Task> operation, object? state, CancellationToken cancellationToken = default)
Copy link
Member

@drewnoakes drewnoakes Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

state params are missing doc nodes in these new overloads. EDIT just seen this is a draft.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya, and a draft that may never be completed.

=> this.ExecuteAsync((object)operation, state, cancellationToken);

/// <summary>
/// Executes a given operation within the semaphore.
/// </summary>
/// <typeparam name="T">The type of value returned by the operation.</typeparam>
/// <param name="operation">
/// The delegate to invoke once the semaphore is entered. If a <see cref="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 <see cref="JoinableTaskContext"/> is supplied to the constructor, this delegate will execute on the
/// caller's context.
/// </param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A task that completes with the result of <paramref name="operation"/>, after the semaphore has been exited.</returns>
public ValueTask<T> ExecuteAsync<T>(Func<object?, ValueTask<T>> operation, object? state, CancellationToken cancellationToken = default)
=> this.ExecuteValueTaskAsync<T>(operation, state, cancellationToken);

/// <summary>
/// Conceals evidence that the caller has entered this <see cref="ReentrantSemaphore"/> till its result is disposed.
Expand All @@ -191,6 +222,33 @@ public void Dispose()
GC.SuppressFinalize(this);
}

/// <summary>
/// Executes a given operation within the semaphore.
/// </summary>
/// <param name="operation">
/// The delegate to invoke once the semaphore is entered. If a <see cref="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 <see cref="JoinableTaskContext"/> is supplied to the constructor, this delegate will execute on the
/// caller's context.
/// </param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A task that completes with the result of <paramref name="operation"/>, after the semaphore has been exited.</returns>
private protected abstract Task ExecuteAsync(object operation, object? state, CancellationToken cancellationToken = default);

/// <summary>
/// Executes a given operation within the semaphore.
/// </summary>
/// <typeparam name="T">The type of value returned by the operation.</typeparam>
/// <param name="operation">
/// The delegate to invoke once the semaphore is entered. If a <see cref="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 <see cref="JoinableTaskContext"/> is supplied to the constructor, this delegate will execute on the
/// caller's context.
/// </param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A task that completes with the result of <paramref name="operation"/>, after the semaphore has been exited.</returns>
private protected abstract ValueTask<T> ExecuteValueTaskAsync<T>(object operation, object? state, CancellationToken cancellationToken = default);

/// <summary>
/// Disposes managed and unmanaged resources held by this instance.
/// </summary>
Expand Down Expand Up @@ -317,7 +375,7 @@ internal NotRecognizedSemaphore(int initialCount, JoinableTaskContext? joinableT
}

/// <inheritdoc />
public override async Task ExecuteAsync(Func<Task> operation, CancellationToken cancellationToken = default)
private protected override async Task ExecuteAsync(object operation, object? state, CancellationToken cancellationToken = default)
{
Requires.NotNull(operation, nameof(operation));

Expand Down Expand Up @@ -371,7 +429,14 @@ await this.ExecuteCoreAsync(async delegate
}
}

await operation().ConfigureAwaitRunInline();
if (operation is Func<Task> func)
{
await func().ConfigureAwaitRunInline();
}
else
{
await ((Func<object?, Task>)operation)(state).ConfigureAwaitRunInline();
}
});
}
finally
Expand All @@ -381,7 +446,7 @@ await this.ExecuteCoreAsync(async delegate
}

/// <inheritdoc />
public override async ValueTask<T> ExecuteAsync<T>(Func<ValueTask<T>> operation, CancellationToken cancellationToken = default)
private protected override async ValueTask<T> ExecuteValueTaskAsync<T>(object operation, object? state, CancellationToken cancellationToken = default)
{
Requires.NotNull(operation, nameof(operation));

Expand Down Expand Up @@ -432,7 +497,14 @@ public override async ValueTask<T> ExecuteAsync<T>(Func<ValueTask<T>> operation,
}
}

return await operation().ConfigureAwait(true);
if (operation is Func<ValueTask<T>> func)
{
return await func().ConfigureAwait(true);
}
else
{
return await ((Func<object?, ValueTask<T>>)operation)(state).ConfigureAwait(true);
}
});
}
finally
Expand Down Expand Up @@ -470,7 +542,7 @@ internal NotAllowedSemaphore(int initialCount, JoinableTaskContext? joinableTask
}

/// <inheritdoc />
public override async Task ExecuteAsync(Func<Task> operation, CancellationToken cancellationToken = default)
private protected override async Task ExecuteAsync(object operation, object? state, CancellationToken cancellationToken = default)
{
Requires.NotNull(operation, nameof(operation));
this.ThrowIfFaulted();
Expand Down Expand Up @@ -529,7 +601,15 @@ await this.ExecuteCoreAsync(async delegate
}

this.reentrancyDetection.Value = ownedBox = new StrongBox<bool>(true);
await operation().ConfigureAwaitRunInline();

if (operation is Func<Task> func)
{
await func().ConfigureAwaitRunInline();
}
else
{
await ((Func<object?, Task>)operation)(state).ConfigureAwaitRunInline();
}
});
}
finally
Expand All @@ -546,7 +626,7 @@ await this.ExecuteCoreAsync(async delegate
}

/// <inheritdoc />
public override async ValueTask<T> ExecuteAsync<T>(Func<ValueTask<T>> operation, CancellationToken cancellationToken = default)
private protected override async ValueTask<T> ExecuteValueTaskAsync<T>(object operation, object? state, CancellationToken cancellationToken = default)
{
Requires.NotNull(operation, nameof(operation));
this.ThrowIfFaulted();
Expand Down Expand Up @@ -604,7 +684,15 @@ public override async ValueTask<T> ExecuteAsync<T>(Func<ValueTask<T>> operation,
}

this.reentrancyDetection.Value = ownedBox = new StrongBox<bool>(true);
return await operation().ConfigureAwait(true);

if (operation is Func<ValueTask<T>> func)
{
return await func().ConfigureAwait(true);
}
else
{
return await ((Func<object?, ValueTask<T>>)operation)(state).ConfigureAwait(true);
}
});
}
finally
Expand Down
Loading