Open EgorBo opened 3 weeks ago
Tagging subscribers to this area: @mangod9 See info in area-owners.md if you want to be subscribed.
In the case of
+public sealed class SemaphoreSlimReleaser(SemaphoreSlim semaphore) : IDisposable
+{
+ public void Dispose() => semaphore.Release();
+}
Would we maybe want an idDisposed
bool field or similar, to prevent double release by disposing the same releaser twice?
I'd make EnterScope
a ValueTask
and change SemaphoreSlimReleaser
to a public struct Scope
nested type (similar to Lock
's scope in #34812).
Also, should it be
EnterScopeAsync
?
My personal opinion - yes
This pattern of issuing a small cookie-object, while changing the state of the owner, and reverting the owner's state on cookie disposal - it's quite familiar and somewhat well-known. I'm so accustomed to it, that I would've chosen the name SemaphoreReleaseCookie
without a hint of hesitation. That said, MSFT uses the term 'cookie' mostly for stack canary scenarios, so it doesn't fit here.
Nevertheless, I would suggest the following:
I've used this pattern so many times, I'm surprised something similar hasn't been proposed before. I think the name should be EnterScopeAsync
, to fit with basically every other API that returns a Task
or ValueTask
.
I have also used this pattern a lot and would welcome something more idiomatic. One thing though, is that in practically all cases I am also using the equivalent sync pattern. It would be nice if both async and sync variants of the pattern were available.
By the way, this exists already in Stephen Cleary's package (https://github.com/StephenCleary/AsyncEx/blob/master/src/Nito.AsyncEx.Tasks/SemaphoreSlimExtensions.cs). If we were going there, it would be even nicer if there were a dedicated type which can participate in synchronous and asynchronous mutual exclusion, including with Monitor.Wait&Pulse-like semantics. Basically, something similar to Cleary's AsyncMonitor/AsyncLock (https://github.com/StephenCleary/AsyncEx/blob/master/src/Nito.AsyncEx.Coordination/AsyncMonitor.cs) but perhaps that's a separate discussion.
I'd make
EnterScope
aValueTask
and changeSemaphoreSlimReleaser
to apublic struct Scope
nested type (similar toLock
's scope in #34812).
Just to point out a potential caveat: If the scope has a field that prevents double-releasing and is a struct, would it not still be possible to release twice if the struct is copied?
Similarily to Lock.EnterScope API.
Given the similarity should we consider expanding the new C# lock
support to include this type? When we started the work on Lock.EnterScope
in was specific to that type because there were no other examples.
Similarily to Lock.EnterScope API.
Given the similarity should we consider expanding the new C#
lock
support to include this type? When we started the work onLock.EnterScope
in was specific to that type because there were no other examples.
I guess the main problem that it's not obvious what it should do, the wait in this case is async, so should it look like this:
lock (obj) // await is implicit, requires method to be async. obj must be SemaphoreSlim (or some duck-typing check)
{
}
or it needs to be a new construct:
await lock (obj)
{
}
I think it should be await lock (...)
analog to using
and foreach
. More problematic is how you can pass the cancellation token and the timeout and how you can .ConfigureAwait
?
Background and motivation
A classic pattern for locks in async code looks like this:
This pattern feels a bit too verbose compared to non-async
lock
and it's important to keep in mind thatRelease
has to be called infinally
block to avoid dead-locks. I propose we add a new API forSemaphoreSlim
so the same program can be simplified down toSimilarily to Lock.EnterScope API.
API Proposal
I am not sure about the overloads, I just copied the exising ones from
WaitAsync
for consistency, I'd pesonally add only these:Also, should it be
EnterScopeAsync
?API Usage
Sadly, C# doesn't allow to omit the variable so we could have something like:
Alternative Designs
We can extend the
lock
keyword in C# to supportasync/await
like we recently extended it to support the newLock
type. I think it's a popular pain point when developers (especially newcomers) try to uselock (obj)
for async code and then have to google how to workaround the compilation failure.Or at very least, Roslyn could suggest to use
SemaphoreSlim
in the error text when user tries to use normallock
for async blocks.Risks
No response