views:

3160

answers:

7

I just realized that in some place in my code I have the return statement inside the lock and sometime outside. Which one is the best?

1)

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

2)

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

}

Which one should I use?

+10  A: 

It doesn't make any difference; they're both translated to the same thing by the compiler.

To clarify, either is effectively translated to something with the following semantics:

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

return myData;
Greg Beech
Well, that is true of the try/finally - however, the return outside the lock still requires extra locals which can't be optimised away - and takes more code...
Marc Gravell
You can't return from a try block; it must end with a ".leave" op-code. So the CIL emitted should be the same in either case.
Greg Beech
You are right - I've just looked at the IL (see updated post). I learnt something ;-p
Marc Gravell
Cool, unfortunately I learned from painful hours trying to emit .ret op-codes in try blocks and having the CLR refuse to load my dynamic methods :-(
Greg Beech
Very nice answer. Thank you.
David Pokluda
I can relate; I've done a fair amount of Reflection.Emit, but I'm lazy; unless I'm very sure about something, I write representative code in C# and then look at the IL. But it is surprising how quickly you start thinking in IL terms (i.e. sequencing the stack).
Marc Gravell
+1  A: 

Outside looks cleaner.

Ovidiu Pacurar
+1  A: 

To make it easier for fellow developers to read the code I would suggest the first alternative.

Adam Asham
+4  A: 

If think the lock outside looks better, but be careful if you end up changing the code to:

return f(...)

If f() needs to be called with the lock held then it obviously needs to be inside the lock, as such keeping returns inside the lock for consistency makes sense.

Rob Walker
+23  A: 

Essentially, which-ever makes the code simpler. Single point of exit is a nice ideal, but I wouldn't bend the code out of shape just to achieve it... And if the alternative is declaring a local variable (outside the lock), initializing it (inside the lock) and then returning it (outside the lock), then I'd say that a simple "return foo" inside the lock is a lot simpler.

To show the difference in IL, lets code:

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;
    }
}

(note that I'd happily argue that ReturnInside is a simpler/cleaner bit of C#)

And look at the IL (release mode etc):

.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
}

So at the IL level they are [give or take some names] identical (I learnt something ;-p). As such, the only sensible comparison is the (highly subjective) law of local coding style... I prefer ReturnInside for simplicity, but I wouldn't get excited about either.

Marc Gravell
How do you get the IL code sir?
Mister Dev
I used the (free and excellent) Red Gate's .NET Reflector (was: Lutz Roeder's .NET Reflector), but ILDASM would do too.
Marc Gravell
One of the most powerful aspects of Reflector is that you can actually disassemble IL to your preferred language (C#, VB, Delphi, MC++, Chrome, etc)
Marc Gravell
Very nice answer Marc. Thank you.
David Pokluda
Very nice answer and very cool that you shoved us the IL.
bovium
For your simple example the IL stays the same, but that is probably because you only return a constant value?! I believe for real life scenarios the result may differ, and parallel threads may cause problems for each other by modifying the value before it's returned it the return statement is outside of the lock block. Dangerous!
Torbjørn
+3  A: 

It depends,

I am going to go against the grain here. I generally would return inside of the lock.

Usually the variable mydata is a local variable. I am fond of declaring local variables while I initialize them. I rarely have the data to initialize my return value outside of my lock.

So your comparison is actually flawed. While ideally the difference between the two options would be as you had written, which seems to give the nod to case 1, in practice its a little uglier.

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

vs.

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

I find case 2 to be considerably easier to read and harder to screw up, especially for short snippets.

Edward Kmett
+2  A: 

I would definitely put the return inside the lock. Otherwise you risk another thread entering the lock and modifying your variable before the return statemet, therefore making the original caller receive a different value than expected.

Ricardo Villamil
This is correct, a point the other responders seem to be missing. The simple samples they have made may produce the same IL, but this is not so for most real-life scenarios.
Torbjørn