我刚刚意识到,在我的代码中的某个地方,我有 return 语句在锁中,有时在锁外。哪个是最好的?
1)
void example() { lock (mutex) { //... } return myData; }
2)
void example() { lock (mutex) { //... return myData; } }
我该用哪个?
这没有什么区别; 它们都被编译器翻译成相同的内容。
为了澄清,任何一种都可以有效地翻译成以下语义:
T myData; Monitor.Enter(mutex) try { myData= // something } finally { Monitor.Exit(mutex); } return myData;
为了方便其他开发人员阅读代码,我建议使用第一种方法。
如果你认为外面的锁看起来更好,但是如果你最终把代码改成:
return f(...)
如果需要在持有锁的情况下调用 f () ,那么它显然需要在锁内部,因为为了保持一致性,在锁内部保持返回是有意义的。
本质上,这使得代码更简单。单点退出是一个不错的理想选择,但是我不会为了实现它而把代码弄弯... ... 如果另一种选择是声明一个局部变量(在锁外) ,初始化它(在锁内) ,然后返回它(在锁外) ,那么我会说,在锁内简单的“ return foo”要简单得多。
为了显示 IL 中的区别,让代码:
static class Program { static void Main() { } static readonly object sync = new object(); static int GetValue() { return 5; } static int ReturnInside() { lock (sync) { return GetValue(); } } static int ReturnOutside() { int val; lock (sync) { val = GetValue(); } return val; } }
(请注意,我很高兴地认为 ReturnInside是 C # 中更简单/更干净的一部分)
ReturnInside
再看看 IL (发布模式等) :
.method private hidebysig static int32 ReturnInside() cil managed { .maxstack 2 .locals init ( [0] int32 CS$1$0000, [1] object CS$2$0001) L_0000: ldsfld object Program::sync L_0005: dup L_0006: stloc.1 L_0007: call void [mscorlib]System.Threading.Monitor::Enter(object) L_000c: call int32 Program::GetValue() L_0011: stloc.0 L_0012: leave.s L_001b L_0014: ldloc.1 L_0015: call void [mscorlib]System.Threading.Monitor::Exit(object) L_001a: endfinally L_001b: ldloc.0 L_001c: ret .try L_000c to L_0014 finally handler L_0014 to L_001b } method private hidebysig static int32 ReturnOutside() cil managed { .maxstack 2 .locals init ( [0] int32 val, [1] object CS$2$0000) L_0000: ldsfld object Program::sync L_0005: dup L_0006: stloc.1 L_0007: call void [mscorlib]System.Threading.Monitor::Enter(object) L_000c: call int32 Program::GetValue() L_0011: stloc.0 L_0012: leave.s L_001b L_0014: ldloc.1 L_0015: call void [mscorlib]System.Threading.Monitor::Exit(object) L_001a: endfinally L_001b: ldloc.0 L_001c: ret .try L_000c to L_0014 finally handler L_0014 to L_001b }
因此,在 IL 层面,它们(给或取一些名称)是相同的(我学到了一些东西; -p)。 因此,唯一合理的比较就是本地编码风格的(高度主观的)规律... ... 为了简单起见,我更喜欢 ReturnInside,但我对这两者都不感兴趣。
看情况,
我要逆着纹路走,通常我会回到锁里面。
通常变量 mydata 是一个局部变量。我喜欢在初始化局部变量时声明它们。我很少有在锁之外初始化返回值的数据。
所以你的比较其实是有缺陷的。虽然理想情况下,这两个选项之间的区别应该和您所写的一样,这似乎是对情况1的肯定,但实际上它有点难看。
void example() { int myData; lock (foo) { myData = ...; } return myData }
对。
void example() { lock (foo) { return ...; } }
我发现情况2更容易阅读,也更难搞砸,特别是对于简短的片段。
我肯定会把退货放进锁里。否则,您将面临另一个线程进入锁并在 return 语句之前修改变量的风险,从而使原始调用方接收到与预期不同的值。
值得一提的是,MSDN 上的文档有一个从锁内部返回的示例。从这里的其他答案来看,它似乎是非常相似的 IL,但是,对我来说,从锁内部返回似乎更安全,因为这样你就不会冒返回变量被另一个线程覆盖的风险。
lock() return <expression>报表总是:
lock() return <expression>
1)进入锁定状态
2)对指定类型的值进行本地(线程安全)存储,
3)用 <expression>返回的值填充存储,
<expression>
4)出口锁
5)归还商店。
它意味着从 lock 语句返回的值在返回之前总是“熟”的。
不要担心 lock() return,不要听任何人在这里)
lock() return
注意: 我相信这个答案事实上是正确的,我希望它也是有帮助的,但我总是乐于根据具体的反馈来改进它。
总结和补充现有答复:
接受的答案表明,无论您在 C # 代码中选择哪种语法形式,在 IL 代码中——因此在运行时—— return不会发生,直到 之后锁被释放。
return
lock
单独地——这个方面是问题的 意外,但可能仍然有意义(Ricardo Villamil 的回答试图解决这个问题,但我认为是错误的)——将 lock语句与 return语句相结合——例如,在一个受保护不被并发访问的块中获取 return的值——只有有意义地“保护”打电话的人作用域中返回的值 如果它一旦获得就不需要保护,它适用于以下场景:
如果返回的值是集合中的一个元素,只需要根据 加入和移除元素进行保护,而不需要根据对 元素本身和/或..。
... 如果返回的值是 值类型或 绳子的实例。
在任何其他情况下,锁定必须由 打电话的人执行,而不是(仅)在方法内部执行。
[1] Theodor Zoulias指出,从技术上讲,将 return放在 try、 catch、 using、 if、 while、 for等声明中也是如此; 然而,lock声明的具体目的可能会引起对真正控制流程的审查,正如这个问题已经被提出并得到很多关注所证明的那样。
try
catch
using
if
while
for
访问一个值类型的实例总是创建一个线程本地的、在堆栈上的副本; 即使字符串在技术上是引用类型的实例,它们的行为实际上也像值类型的实例。