Lock
object feature roslyn#71716(This proposal comes from @kouvel. I've populated this issue primarily with text he wrote in a separate document and augmented it with a few more details.)
SummaryEnable types to define custom behaviors for entering and exiting a lock when an instance of the type is used with the C# “lock” keyword.
Motivation.NET 9 is likely to introduce a new dedicated System.Threading.Lock type. Along with other custom locks, the presence of the lock
keyword in C# might lead developers to think they can use it in conjunction with this new type, but doing so won't actually lock according to the semantics of the lock type and would instead treat it as any arbitrary object for use with Monitor
.
A type would expose the following to match the proposed pattern:
class Lock : ILockPattern { public Scope EnterLockScope(); public ref struct Scope { public void Dispose(); } } public interface ILockPattern { }
EnterLockScope()
would enter the lock and Dispose()
would exit the lock. The behaviors of entering and exiting the lock are defined by the type.
The ILockPattern
interface is a marker interface that indicates that usage of values of this type with the lock
keyword would override the normal code generation for arbitrary objects. Instead, the compiler would lower the lock
to use the lock pattern, e.g.:
class MyDataStructure { private readonly Lock _lock = new(); void Foo() { lock (_lock) { // do something } } }
would be lowered to the equivalent of:
class MyDataStructure { private readonly Lock _lock = new(); void Foo() { using (_lock.EnterLockScope()) { // do something } } }Lock pattern and behavior details
Consider a type L
(Lock
in this example) that may be used with the lock
keyword. If L
matches the lock pattern, it would meet all of the following criteria:
L
implements interface ILockPattern
L
has an accessible instance method S EnterLockScope()
that returns a value of type S
(Lock.Scope
in this example). The method must be at least as visible as L
. Extension methods don't qualify for the pattern.S
qualifies for use with the using
keywordA marker interface ILockPattern
is used to opt into the behaviors below, including through inheritance, and so that S
may be defined by the user (for instance, as a ref struct
). For a type L
that implements interface ILockPattern
:
L
does not fully match the lock pattern, it would result in an errorS
may not be used in the context, it would result in an errorL
is implicitly or explicitly casted to another type that does not match the lock pattern (including generic types), it would result in a warning
this
to a base type when calling an inherited methodMonitor
with values of type L
that may be masked under a different type and used with the lock
keyword, such as with casts to base types, interfaces, etc.L
is used with the lock
keyword, or a value of type S
is used with the using
keyword, and the block contains an await
, it would result in an error
Monitor
and Lock
have thread affinity.SpinLock
is optionally thread-affinitized. It can opt into the pattern, but then usage of it with the lock
or using
keywords would still disallow awaits inside the block statically.System.Threading.SpinLock
(a struct) could expose such a holder:
struct SpinLock : ILockPattern { [UnscopedRef] public Scope EnterLockScope(); public ref struct Scope { public void Dispose(); } }
and then similarly be usable with lock
whereas today as a struct it's not. When a variable of a struct type that matches the lock pattern is used with the lock
keyword, it would be used by reference. Note the reference to SpinLock
in the previous section.
lock(objectsOfThatType)
in existing code, and in ways that might go unnoticed due to the general unobservability of locks (when things are working). This also means if System.Threading.Lock is introduced before the language support, it likely couldn't later participate.
L1
that exposes the type is updated to opt into the lock pattern, another library L2
that references L1
and was compiled for the previous version of L1
would have different locking behavior until it is recompiled with the updated version of L1
.using
instead of lock
and writing out the name of the enter method manually (i.e. writing out the lowered code in the first example).ILockPattern
itself doesn't match the lock pattern, though an interface could be created to match the lock pattern.AttributeUsageAttribute.Inherited = true
could be used for an attribute, it doesn't seem to work with interfaces.using lock(x)
or other alternatives instead, which would eliminate any possibility of misusing values of the type with Monitor
.ref
keyword for certain uses, e.g. in the SpinLock example.https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-01.md#lock-statement-improvements
https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-16.md#lock-statement-pattern
https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-12-04.md#lock-statement-pattern
orthoxerox, timcassell, Unknown6656, jnm2, Rekkonnect and 18 moreIS4Code, miyu, fowl2, TonyValenti, vzarytovskii and 10 moreTahirAhmadov, miyu, koszeggy, OndrejPetrzilka and huoshan12345ViRuSTriNiTy
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4