注意
本文是特性规范。 该规范充当该功能的设计文档。 它包括建议的规范更改,以及功能设计和开发过程中所需的信息。 这些文章将发布,直到建议的规范更改最终确定并合并到当前的 ECMA 规范中。
功能规范与已完成的实现之间可能存在一些差异。 这些差异记录在相关的语言设计会议 (LDM) 说明中。
可以在 规范一文中详细了解将功能规范采用 C# 语言标准的过程。
支持者问题:https://github.com/dotnet/csharplang/issues/7104
总结
System.Threading.Lock 如何与 lock 关键字交互的特殊情况(在后台调用其 EnterScope 方法)。
添加静态分析警告,以防止尽可能意外滥用该类型。
动机
.NET 9 引入了新的 System.Threading.Lock 类型,作为现有基于监视器的锁定的更好替代方法。
C# 中存在 lock 关键字可能会导致开发人员认为他们可以将此关键字用于此新类型。
这样做不会根据该类型特定定义的语义进行锁定,而是将其视为普通对象,并使用监视器锁定机制。
namespace System.Threading
{
public sealed class Lock
{
public void Enter();
public void Exit();
public Scope EnterScope();
public ref struct Scope
{
public void Dispose();
}
}
}
详细设计
lock 语句 (§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是 reference_type的表达式,其精确等效于: ~
请注意,如果 Lock 类型未 sealed,则形状可能不会完全检查(例如,如果 Lock 类型未 sealed,则不会显示任何错误和警告),但该功能可能无法按预期工作(例如,将 转换为派生类型时不会有警告,因为该功能假定没有派生类型)。
此外,在向上转换 类型时,隐式引用转换 (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), - 直接使用
MonitorAPI, - 间接强制转换,如
object AsObject<T>(T l) => (object)l;。
替代方案
支持一种通用模式,其他类型也可以使用该模式与
lock关键字进行交互。 这是一项未来的工作,可能会在ref struct可以参与泛型时实现。 LDM 2023-12-04 中进行了讨论。为了避免现有基于监视器的锁定和新
Lock(或将来的模式)之间存在歧义,我们可以:- 引入新语法,而不是重用现有
lock语句。 - 要求使用新的锁类型
structs,因为现有的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的限制- 对于新的
Lock类型(只有当 API 提议将其从class更改为struct时才需要。) - 对于未来实现时任何类型都可以参与的通用模式。
- 对于新的
我们可以在
lock方法中允许新的async,其中await不在lock中使用。- 目前,由于
lock已降低到using,ref struct作为资源,因此会导致编译时错误。 解决方法是将lock提取到单独的非async方法中。 - 我们可以在
ref struct ScopeLock.EnterLock.Exit中发出try和 / 方法,而不是使用finally。 然而,当从与Exit不同的线程调用Enter方法时,它必须抛出,因此它包含一个线程查找,而使用Scope时可以避免。 - 如果
using正文中没有ref struct,最好允许在async方法中的await上编译using。
- 目前,由于
设计会议
-
LDM 2023-05-01:支持
lock模式的初始决策 - LDM 2023-10-16:分类到 .NET 9 的工作集
-
LDM 2023-12-04:拒绝了常规模式,仅接受
Lock类型的特殊情况处理 + 添加静态分析警告。