返回声明应该在锁定内部还是外部?

时间:2008-11-05 21:10:26

标签: c# .net multithreading mutex

我刚刚意识到,在我的代码中的某个地方,我在锁内部有一个return语句,有时在外面。哪一个最好?

1)

void example()
{
    lock (mutex)
    {
    //...
    }
    return myData;
}

2)

void example()
{
    lock (mutex)
    {
    //...
    return myData;
    }

}

我应该使用哪一个?

10 个答案:

答案 0 :(得分:170)

基本上,它使代码变得更简单。单点退出是一个很好的理想,但我不会为了实现它而弯曲代码变形...如果替代方案是声明一个局部变量(在锁外),初始化它(在锁内)和然后返回它(在锁外),然后我会说锁中的一个简单的“返回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,但我不会对此感到兴奋。

答案 1 :(得分:37)

它没有任何区别;它们都被编译器翻译成同样的东西。

澄清一下,要么被有效地翻译成具有以下语义的东西:

T myData;
Monitor.Enter(mutex)
try
{
    myData= // something
}
finally
{
    Monitor.Exit(mutex);
}

return myData;

答案 2 :(得分:30)

我肯定会把回报放在锁内。否则,您可能会冒另一个线程进入锁定并在return语句之前修改变量,从而使原始调用者获得的值与预期值不同。

答案 3 :(得分:5)

如果认为外面的锁看起来更好,但是如果最终将代码改为:

,请小心
return f(...)

如果需要在持有锁的情况下调用f(),那么它显然需要在锁内部,因此保持在锁内部返回以保持一致性是有道理的。

答案 4 :(得分:4)

取决于,

我要反对这里的粮食。我一般都会回到锁内。

通常变量mydata是局部变量。我喜欢在初始化时声明局部变量。我很少有数据来初始化我的锁定之外的返回值。

所以你的比较实际上是有缺陷的。虽然理想情况下两个选项之间的区别就像你写的那样,这似乎是对案例1的点头,但实际上它有点丑陋。

void example() { 
    int myData;
    lock (foo) { 
        myData = ...;
    }
    return myData
}

VS

void example() { 
    lock (foo) {
        return ...;
    }
}

我发现案例2更容易阅读,更难搞砸,特别是对于简短的片段。

答案 5 :(得分:1)

为了让其他开发人员更容易阅读代码,我建议第一个替代方案。

答案 6 :(得分:1)

对于它的价值,documentation on MSDN有一个从锁内部返回的例子。从这里的其他答案来看,它确实看起来非常相似IL,但对我来说,从锁内部返回似乎更安全,因为那样你就不会冒被另一个线程覆盖的返回变量的风险。 / p>

答案 7 :(得分:0)

外面看起来更干净。

答案 8 :(得分:0)

始终

lock() return <expression>语句:

1)输入锁定

2)为指定类型的值

创建本地(线程安全)存储

3)使用<expression>

返回的值填充商店

4)退出锁定

5)退货。

这意味着从lock语句返回的值在返回之前总是“熟”。

不要担心lock() return,不要在这里听任何人))

答案 9 :(得分:-2)

注意:我相信这个答案在事实上是正确的,我希望它也会有所帮助,但是我总是很乐意根据具体反馈来改进它。

总结并补充现有的答案:

  • accepted answer显示,无论您在 C#代码中选择哪种语法形式,都在IL代码中-因此在运行时-return直到 锁定被释放后才发生。

    • 因此,即使严格地将return放置在lock块内{em},return块仍然错误地表示了控制流 [1] 从语法上讲便捷,因为它不需要将返回值存储在aux中。局部变量(在块外部声明,以便可以与块外部的lock一起使用)-请参见Edward KMETT's answer
  • 分别-和该问题偶然有关,但可能仍然很有趣(Ricardo Villamil's answer试图解决这一问题,但我认为是错误的)-结合了return语句和return语句-即在防止并发访问的块中获取return的值-仅有意义地“保护” 调用者的范围如果获得后实际上并不需要保护,则该范围适用于以下情况:

    • 如果返回的值是集合中的元素,则只需要在添加和删除元素方面进行保护,而无需在元素本身进行修改方面进行保护>和/或...

    • ...,如果返回的值是值类型 string 的实例。

      • 请注意,在这种情况下,调用方会收到该值的快照(副本) [2] -在调用方检查时,该值可能不再是源数据结构中的当前值。
    • 在任何其他情况下,锁定都必须​​由调用方执行,而不是(仅)在方法内部进行。


[1] Theodor Zoulias指出,将try放在catchusingif,{{ 1}},whilefor,...语句;但是,lock语句的特定目的可能会引起对真正控制流的仔细检查,这一问题已被问到并引起了广泛关注。

[2]访问值类型实例始终会为其创建线程本地的堆栈副本;即使字符串从技术上讲是引用类型的实例,它们也有效地表现类似值的类型的实例。