我只是意识到在代码的某些地方,return语句位于锁内部,有时位于外部。哪一个是最好的?
1)
void example()
{
lock (mutex)
{
//...
}
return myData;
}
2)
void example()
{
lock (mutex)
{
//...
return myData;
}
}
我应该使用哪一个?
我只是意识到在代码的某些地方,return语句位于锁内部,有时位于外部。哪一个是最好的?
1)
void example()
{
lock (mutex)
{
//...
}
return myData;
}
2)
void example()
{
lock (mutex)
{
//...
return myData;
}
}
我应该使用哪一个?
Answers:
本质上,无论哪种方法都使代码更简单。单点退出是一个很好的理想选择,但是我不会为了实现它而使代码变形……并且如果替代方法是声明局部变量(在锁外),对其进行初始化(在锁内)并然后将其返回(在锁之外),那么我想说,锁内的简单“ 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#的更简单/更简洁的用法)
并查看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
为简化起见,我更喜欢,但我都不会对此感到兴奋。
ret
在.try
区域内。
没什么区别;它们都被编译器翻译成同一件事。
为了澄清起见,其中任何一个都有效地转换为具有以下语义的内容:
T myData;
Monitor.Enter(mutex)
try
{
myData= // something
}
finally
{
Monitor.Exit(mutex);
}
return myData;
我一定会把收益放在锁里。否则,您可能要冒另一个线程进入锁并在return语句之前修改变量的风险,从而使原始调用方收到的值不同于预期。
这取决于,
我要反对这里的粮食。我通常会回到锁内。
通常,变量mydata是局部变量。我喜欢在初始化局部变量时声明它们。我很少有数据可以在锁之外初始化返回值。
因此,您的比较实际上是有缺陷的。理想情况下,这两个选项之间的差异将与您所写的相同,这似乎使情况1得到了点头,实际上它有点难看。
void example() {
int myData;
lock (foo) {
myData = ...;
}
return myData
}
与
void example() {
lock (foo) {
return ...;
}
}
我发现案例2相当容易阅读,而且很难弄乱,特别是对于短片段。
为了使其他开发人员更容易阅读代码,我建议使用第一种方法。
注意:我相信这个答案实际上是正确的,并且希望它也对您有所帮助,但是我总是很乐意根据具体的反馈来改进它。
总结和补充现有的答案:
该接受的答案显示,不论其语法形式的你在你的选择,C#代码,在IL代码-因此在运行时-在return
没有发生,直到后锁被释放。
return
放置在lock
块中也不能正确表示控制流[1],但从语法上讲,它的方便之处在于它无需将返回值存储在aux中。局部变量(在块外部声明,以便可以return
在块外部使用)-请参见Edward KMETT的答案。另外-这是问题的附带内容,但可能仍然很有趣(里卡多·比利亚米尔(Ricardo Villamil)的答案试图解决这个问题,但我认为这是错误的)-将一个lock
语句与一个return
语句组合在一起-即return
在一个受保护的块中获取价值并发访问-仅在获得返回值后实际上不需要保护时才有意义地“保护” 调用方范围内的返回值,这适用于以下情况:
如果返回值是集合中的元素,则仅需要在添加和删除元素方面进行保护,而在修改元素本身和/或... 方面则不需要进行保护。
...如果返回的值是值类型或字符串的实例。
在任何其他情况下,锁定必须由调用者执行,而不是(仅)在方法内部执行。
[1] 西奥多Zoulias指出,在技术上也是如此放置return
内try
,catch
,using
,if
,while
,for
,...报表; 但是,lock
陈述的具体目的很可能会引起对真正控制流程的审查,这一问题已被问到并引起了广泛关注,这证明了这一点。
[2]访问值类型实例总是会创建它在线程本地的堆栈上的副本;即使字符串从技术上来说是引用类型的实例,它们也有效地表现类似值的类型的实例。
lock
,并从return语句的位置得出含义。这是与这个问题恕我直言无关的讨论。另外,我发现“不实陈述”的使用也令人不安。如果从返回lock
歪曲控制流,那么同样可以说从一回try
,catch
,using
,if
,while
,for
,和语言的任何其他结构。就像说C#充斥着控制流错误表示。耶稣...
try
,,if
...我个人甚至都不考虑这个问题,但是lock
,特别是在的背景下,这个问题对我来说是个问题-如果其他人也没有想到,就永远不会问这个问题,并且接受的答案也不会花很多时间去调查真实的行为。