объект Lock
Заметка
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она включает предлагаемые изменения спецификации, а также информацию, необходимую на этапах проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.
Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Различия отражены в соответствующих заметках собраний по проектированию языка (LDM) .
Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .
Проблема чемпиона: https://github.com/dotnet/csharplang/issues/7104
Сводка
Особый случай взаимодействия System.Threading.Lock
с ключевым словом lock
(вызов метода EnterScope
неявно).
Добавьте предупреждения статического анализа, чтобы предотвратить случайное неправильное использование типа, где это возможно.
Мотивация
.NET 9 вводит новый тип System.Threading.Lock
в качестве лучшей альтернативы существующей блокировке на основе монитора.
Наличие ключевого слова lock
в C# может привести разработчиков к тому, что они могут использовать его с этим новым типом.
Это не будет блокироваться в соответствии с семантикой этого типа, но вместо этого будет рассматриваться как любой другой объект и будет использовать блокировку на основе монитора.
namespace System.Threading
{
public sealed class Lock
{
public void Enter();
public void Exit();
public Scope EnterScope();
public ref struct Scope
{
public void Dispose();
}
}
}
Подробный дизайн
Семантика инструкции блокировки (§13.13) изменяется, чтобы учитывать особый случай для типа System.Threading.Lock
:
Инструкция
lock
формыlock (x) { ... }
- , где
x
является выражением типаSystem.Threading.Lock
, точно эквивалентно:иusing (x.EnterScope()) { ... }
System.Threading.Lock
должны иметь следующую форму:namespace System.Threading { public sealed class Lock { public Scope EnterScope(); public ref struct Scope { public void Dispose(); } } }
- где
x
— это выражение типа ссылки , и это точно эквивалентно: [...]
Обратите внимание, что форма может не быть полностью проверена (например, не будет ошибок или предупреждений, если тип Lock
не является sealed
), но функция может не работать так, как ожидалось (например, не будет предупреждений, когда тип Lock
преобразуется в производный, так как функция предполагает отсутствие производных типов).
Кроме того, новые предупреждения добавляются в неявные преобразования ссылок (§10.2.8) при переадресовии типа System.Threading.Lock
:
Неявные преобразования ссылок:
- От любого reference_type до
object
иdynamic
.
- Предупреждение сообщается, когда reference_type, как известно,
System.Threading.Lock
.- От любого class_type
S
до любого class_typeT
, еслиS
является производным отT
.
- выдаётся предупреждение, когда
S
известно какSystem.Threading.Lock
.- От любых class_type
S
к любым interface_typeT
, при условии, чтоS
реализуетT
.
- выдается предупреждение, когда известно, что
S
являетсяSystem.Threading.Lock
.- [...]
object l = new System.Threading.Lock(); // warning
lock (l) { } // monitor-based locking is used here
Обратите внимание, что это предупреждение возникает даже для эквивалентных явных преобразований.
Компилятор не сообщает предупреждение в некоторых случаях, когда экземпляр не может быть заблокирован после преобразования в object
:
- Если преобразование неявно и является частью вызова оператора равенства объектов.
var l = new System.Threading.Lock();
if (l != null) // no warning even though `l` is implicitly converted to `object` for `operator!=(object, object)`
// ...
Чтобы выйти из предупреждения и принудительного использования блокировки на основе монитора, можно использовать
- обычные средства подавления предупреждений (
#pragma warning disable
) -
Monitor
API напрямую, - неявное приведение, например
object AsObject<T>(T l) => (object)l;
.
Альтернативы
Поддержка общего шаблона, который другие типы также могут использовать для взаимодействия с ключевым словом
lock
. Это будущая работа, которая может быть реализована, когдаref struct
s может участвовать в универсальных шаблонах. Это обсуждалось в LDM 2023-12-04.Чтобы избежать неоднозначности между мониторной блокировкой и новыми элементами типа
Lock
(или шаблоном в будущем), мы могли бы:- Введите новый синтаксис вместо повторного использования существующей инструкции
lock
. - Требовать, чтобы новые типы блокировки были
struct
(так как существующиеlock
запрещают типы значений). Могут возникнуть проблемы с конструкторами по умолчанию и копированием, если структуры имеют отложенную инициализацию.
- Введите новый синтаксис вместо повторного использования существующей инструкции
Генератор кода может быть защищен от завершения потоков (которые сами устарели).
Мы также могли бы предупредить, когда
Lock
передается в качестве параметра типа, потому что блокировка на параметре типа всегда использует блокировку на основе монитора.M(new Lock()); // could warn here void M<T>(T x) // (specifying `where T : Lock` makes no difference) { lock (x) { } // because this uses Monitor }
Однако это может вызвать предупреждения при хранении
Lock
в списке, что нежелательно.List<Lock> list = new(); list.Add(new Lock()); // would warn here
Можно включить статический анализ, чтобы предотвратить использование
System.Threading.Lock
вusing
иawait
. То есть мы можем выдать ошибку или предупреждение для кода, например,using (lockVar.EnterScope()) { await ... }
. В настоящее время это не требуется, так какLock.Scope
являетсяref struct
, поэтому код является незаконным в любом случае. Однако, если бы мы когда-либо разрешили использоватьref struct
в методахasync
или изменилиLock.Scope
, чтобы он не былref struct
, этот анализ стал бы полезным. (Мы также, вероятно, должны рассмотреть для этого все типы блокировки, соответствующие общему шаблону, в случае если они будут реализованы в будущем. Хотя может возникнуть необходимость в механизме отказа, поскольку некоторые типы блокировок могут быть разрешены для использования сawait
.) Кроме того, это может быть реализовано в виде анализатора, включенного в среду выполнения.Мы могли бы ослабить ограничение, согласно которому типы значений не могут быть
lock
ed.- для нового типа
Lock
(только если предложение API изменило его сclass
наstruct
), - для общего шаблона, в котором при реализации в будущем сможет участвовать любой тип.
- для нового типа
Мы могли бы разрешить новый
lock
в методахasync
, гдеawait
не используется внутриlock
.- В настоящее время, так как
lock
снижается доusing
сref struct
в качестве ресурса, это приводит к ошибке во время компиляции. Решение заключается в извлеченииlock
в отдельный метод, не относящийся кasync
. - Вместо использования
ref struct Scope
мы могли бы выпустить методыLock.Enter
иLock.Exit
вtry
/finally
. Однако методExit
должен вызываться при вызове из другого потока, отличного отEnter
, поэтому он содержит подстановку потока, которая избегается при использованииScope
. - Лучше всего разрешить компиляцию
using
наref struct
в методахasync
, если в телеusing
отсутствуетawait
.
- В настоящее время, так как
Совещания по проектированию
-
LDM 2023-05-01: первоначальное решение о поддержке шаблона
lock
- LDM 2023-10-16: включено в рабочий набор для .NET 9
-
LDM 2023-12-04: отклонил общий шаблон, приняв только специальные случаи для типа
Lock
и добавление предупреждений статического анализа
C# feature specifications