对于以下代码,我得到编译时错误*
'int'不是引用类型 锁定声明所需的
int i = 0;
lock(i);
但没有错误:
int i = 0;
Monitor.Enter(i);
据我所知,由于拳击造成的并发症,不应使用值类型进行锁定。但是,为什么它适用于Monitor。
答案 0 :(得分:15)
原因是lock是一种语言构造,编译器选择在表达式上强加额外的语义。 Monitor.Enter只是一个方法调用,C#编译器不会以任何方式调用特殊情况,因此它会通过正常的重载解析和装箱。
答案 1 :(得分:12)
您绝对不应在Monitor.Enter
上使用int
。它起作用的原因是因为int
被装箱,所以除非你存储对盒装值的引用,否则你将锁定一个临时对象,这意味着你不能在没有异常的情况下调用Monitor.Exit
建议的锁定方法是创建private readonly object
并锁定它。对于静态方法,您可以使用private static object
。
答案 2 :(得分:6)
编译器规范定义the behaviour of lock like so:
lock语句表达式的编译时类型应为reference-type或>类型参数(第25.1.1节)已知为引用类型。这是一个编译时错误 编译表达式的时间类型以表示值类型。
然后,只要它编译
,它就定义它等同于由于Monitor.Exit只是一个没有任何约束的方法调用,因此它不会阻止编译器自动装入int并继续其快乐(并且非常)错误的方式。
lock
不仅仅是语法糖,foreach
不仅仅是语法糖。由此产生的IL转换 not 呈现给代码的其余部分,就好像那是已写的一样。
在foreach
中修改迭代变量是违法的(尽管在结果输出代码中IL级别没有任何内容可以阻止这种情况)。在锁定中,编译器会阻止编译时已知的值类型,尽管导致IL不关心这一点。
暂且不说:
从理论上讲,编译器可以通过对这个(和其他)方法的深入了解来“祝福”,以便它发现这种情况发生的明显情况,但从根本上说,不可能总是在编译时发现它(考虑到从另一个方法,汇编或通过反射传入的对象)因此烦恼发现任何此类情况可能会适得其反。
编译器对API内部的了解越多,如果您希望将来更改API,您将遇到的问题就越多。
例如,可以添加Monitor.Enter()的重载,该重载接受int并锁定在与int值关联的进程范围的互斥锁上。
这将符合监视器的规格(即使它可能是可怕的),但会导致旧编译器的大量问题,仍然快乐地阻止已经合法的操作。
答案 3 :(得分:3)
出于好奇,你对变量'i'做了什么需要它被锁定?如果所有操作都是增量或其他内容,则使用Interlocked类可能更有效:
Interlocked.Increment(i); // i++ in a thread safe manner
Interlocked类是.NET提供的最轻量级线程同步工具,对于简单的增量,减量,读取或交换,它是最佳选择。
如果您尝试同步一个行为块,那么我只需创建一个可用作同步根的对象:
object syncRoot = new object();
// ...
lock(syncRoot)
{
// put synced behavior here
}
答案 4 :(得分:0)
我想这是因为Monitor.Enter()
是方法调用,所以编译器会自动执行装箱,而lock()
是一个语法元素,所以编译器可以检查并在值类型上抛出错误