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

Pat*_*ins 134 .net c# multithreading mutex

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

1)

void example()
{
    lock (mutex)
    {
    //...
    }
    return myData;
}
Run Code Online (Sandbox Code Playgroud)

2)

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

}
Run Code Online (Sandbox Code Playgroud)

我应该使用哪一个?

Mar*_*ell 179

从本质上讲,这使代码更简单.单点退出是一个很好的理想,但我不会为了实现它而弯曲代码变形...如果替代方案是声明一个局部变量(在锁外),初始化它(在锁内)和然后返回它(在锁外),然后我会说锁中的一个简单的"返回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;
    }
}
Run Code Online (Sandbox Code Playgroud)

(请注意,我很高兴地认为这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
}
Run Code Online (Sandbox Code Playgroud)

因此,在IL级别,他们[给予或采取一些名称]相同(我学到了一些东西;-p).因此,唯一明智的比较是局部编码风格的(高度主观)法则......我更喜欢ReturnInside简单,但我不会对此感到兴奋.

  • 我使用了(免费且优秀的)Red Gate的.NET Reflector(是:Lutz Roeder的.NET Reflector),但是ILDASM也会这样做. (15认同)
  • 对于你的简单例子,IL保持不变,但这可能是因为你只返回一个常量值?!我相信对于现实生活场景,结果可能会有所不同,并行线程可能会在返回之前修改值,从而导致彼此出现问题,return语句位于锁定块之外.危险! (3认同)

Gre*_*ech 39

它没有任何区别; 它们都被编译器翻译成同一个东西.

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

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

return myData;
Run Code Online (Sandbox Code Playgroud)

  • 你不能从try块返回; 它必须以".leave"操作码结尾.所以在任何一种情况下发出的CIL应该是相同的. (3认同)
  • 你是对的 - 我刚刚看了IL(见更新后的帖子).我学到了一些东西;-p (3认同)
  • 好吧,try/finally 确实如此——然而,锁外的返回仍然需要额外的无法优化掉的局部变量——并且需要更多的代码...... (2认同)
  • 很酷,不幸的是,我从痛苦的几个小时中尝试在try块中发出.ret操作码并让CLR拒绝加载我的动态方法:-( (2认同)

Ric*_*mil 32

我肯定会把回报放在锁内.否则,您冒着另一个线程进入锁定并在return语句之前修改变量的风险,因此使原始调用者收到的值与预期不同.

  • 这是正确的,其他响应者似乎缺少一点.他们制作的简单样本可能产生相同的IL,但对于大多数现实生活场景而言并非如此. (4认同)
  • 我很惊讶其他答案不谈这个 (4认同)
  • 在这个示例中,他们讨论的是使用堆栈变量来存储返回值,即只有锁定之外的return语句,当然还有变量声明.另一个线程应该有另一个堆栈,因此不会造成任何伤害,对吗? (3认同)
  • 这个答案是不正确的。另一个线程不能更改局部变量。局部变量存储在栈中,每个线程都有自己的栈。顺便说一句,线程堆栈的默认大小是[1 MB](/sf/ask/2005981071/)。 (3认同)
  • 我认为这不是有效的观点,因为另一个线程可以在返回调用与返回值实际分配给主线程上的变量之间更新值。无论哪种方式,返回的值都不能更改,或者不能保证与当前实际值一致。对? (2认同)

Rob*_*ker 5

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

return f(...)
Run Code Online (Sandbox Code Playgroud)

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


Edw*_*ETT 5

这取决于,

我要在这里反对。我通常会在锁内返回。

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

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

void example() { 
    int myData;
    lock (foo) { 
        myData = ...;
    }
    return myData
}
Run Code Online (Sandbox Code Playgroud)

对比

void example() { 
    lock (foo) {
        return ...;
    }
}
Run Code Online (Sandbox Code Playgroud)

我发现案例 2 更容易阅读并且更难搞砸,尤其是对于短片。