为什么不允许锁定(),但允许使用Monitor.Enter()?

对于以下代码,我得到编译时错误,*

‘int’不是lock语句所要求的引用类型

int i = 0; lock(i); 

但没有错误:

 int i = 0; Monitor.Enter(i); 

据我所知,由于拳击造成的并发症,不应使用值类型进行锁定。 但是,为什么它与Monitor一起使用。

原因是lock是一种语言结构,编译器选择在表达式上强加额外的语义。 Monitor.Enter只是一个方法调用,C#编译器不会以任何方式调用特殊情况,因此它会通过正常的重载解析和装箱。

你绝对不应该在int上使用Monitor.Enter 。 它工作的原因是因为int是盒装的,所以除非你存储对盒装值的引用,否则你将锁定一个临时对象,这意味着你不能在没有exception的情况下调用Monitor.Exit

建议的锁定方法是创建一个private readonly object并锁定它。 对于静态方法,您可以使用private static object

编译器的规范定义了锁的行为,如下所示 :

lock语句表达式的编译时类型应为reference-type或> type参数(第25.1.1节),已知为引用类型。 表达式的编译时类型表示值类型是编译时错误。

然后,只要它编译,它就定义了它的等价物

由于Monitor.Exit只是一个没有任何约束的方法调用,它不会阻止编译器自动装箱int并继续其快乐(和非常)错误的方式。

lock不仅仅是语法糖,就像foreach不仅仅是语法糖一样。 生成的IL变换不会呈现给代码的其余部分,就好像这是已编写的内容。

foreach中修改迭代变量是违法的(尽管在结果输出代码中IL级别没有任何内容可以阻止这种情况)。 在锁定中,编译器会阻止编译时已知值类型,尽管导致IL不关心这一点。

作为旁白:
从理论上讲,编译器可以“熟悉”这个(和其他)方法的深入了解,以便发现这种情况的明显情况,但从根本上说,在编译时总是不可能发现这一点(考虑从另一个方法传入的对象,assembly或通过reflection)如此困扰以发现任何此类情况可能会适得其反。

编译器对API内部的了解越多,如果您希望将来更改API,您将遇到的问题就越多。

例如,可以添加Monitor.Enter()的重载,该重载接受int并锁定在与int值关联的进程范围的互斥锁上。
这将符合监视器的规范(即使它可能是可怕的),但是会对旧编译器造成大量问题,仍然可以快速地阻止已经合法的操作。

出于好奇,你对变量’i’做了什么需要它被锁定? 如果所有操作都是增量或其他内容,则使用Interlocked类可能更有效:

 Interlocked.Increment(i); // i++ in a thread safe manner 

Interlocked类是.NET提供的最轻量级线程同步工具,对于简单的增量,减量,读取或交换,它是最佳选择。

如果您尝试同步一个行为块,那么我只需创建一个可用作同步根的对象:

 object syncRoot = new object(); // ... lock(syncRoot) { // put synced behavior here } 

我想这是因为Monitor.Enter()是一个方法调用,所以编译器自动执行装箱,而lock()是一个语法元素,因此编译器可以检查并在值类型上抛出错误。