2008-11-05 12 views
111

Me acabo de dar cuenta de que en algún lugar de mi código tengo la declaración de devolución dentro de la cerradura y en algún momento afuera. ¿Cuál es el mejor?¿Debería una declaración de devolución estar dentro o fuera de un candado?

1)

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

2)

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

} 

¿Cuál debo usar?

+0

¿Qué hay de disparar reflector y hacer alguna comparación IL ;-). –

+6

@Pop: hecho - ninguno es mejor en términos de IL - solo se aplica el estilo C# –

+1

Muy interesante, ¡wow, aprendo algo hoy! – Pokus

Respuesta

155

Esencialmente, lo que hace que el código sea más simple. El único punto de salida es un buen ideal, pero no desviaría el código para lograrlo ... Y si la alternativa es declarar una variable local (fuera del bloqueo), inicializarla (dentro del bloqueo) y luego lo devuelvo (fuera de la cerradura), entonces diría que un simple "retorno foo" dentro de la cerradura es mucho más simple.

para mostrar la diferencia en IL, permite código:

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

(tenga en cuenta que felizmente yo diría que ReturnInside es un poco más simple/limpiador de C#)

y observe el IL (modo de lanzamiento, 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 
} 

por lo tanto en el nivel de IL son [más o menos] algunos nombres idénticos (I aprendido algo ;-P). Como tal, la única comparación razonable es la ley (altamente subjetiva) del estilo de codificación local ... Prefiero ReturnInside por simplicidad, pero tampoco me entusiasmaría.

+1

¿Cómo se obtiene el IL? código señor? –

+11

Utilicé el (gratis y excelente) Reflector .NET de Red Gate (era: Reflector .NET de Lutz Roeder), pero ILDASM también lo haría. –

+1

Uno de los aspectos más poderosos de Reflector es que realmente puedes desensamblar IL a tu idioma preferido (C#, VB, Delphi, MC++, Chrome, etc.) –

34

No hace ninguna diferencia; ambos son traducidos a la misma cosa por el compilador.

Para aclarar, o bien se traduce efectivamente a algo con la semántica siguiente:

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

return myData; 
+1

Bueno, eso es cierto para el try/finally; sin embargo, el retorno fuera del bloqueo aún requiere locales adicionales que no se pueden optimizar de distancia y toma más código ... –

+2

No puede regresar desde un bloque try; debe terminar con un código de operación ".leave". Entonces el CIL emitido debería ser el mismo en cualquier caso. –

+2

Tienes razón: acabo de mirar el IL (ver publicación actualizada). Aprendí algo ;-p –

0

exterior tiene un aspecto más limpio.

+0

¿Qué sucede cuando el hilo actual sale de la cerradura y entra el siguiente hilo esperando en línea, cambia la variable que devuelve antes de ser devuelto? –

1

Para que sea más fácil para otros desarrolladores leer el código, sugeriría la primera alternativa.

5

Si cree que el bloqueo fuera se ve mejor, pero tenga cuidado si al final cambiar el código para:

return f(...) 

Si f() debe ser llamada con el bloqueo mantenido entonces, obviamente, tiene que estar en el interior el bloqueo, como tal, mantener devoluciones dentro de la cerradura por coherencia tiene sentido.

4

Depende,

voy a ir contra la corriente aquí. Por lo general, volvería dentro de la cerradura.

Por lo general, la variable mydata es una variable local. Me gusta declarar variables locales mientras las inicializo. Raramente tengo los datos para inicializar mi valor de devolución fuera de mi bloqueo.

Por lo tanto, su comparación es realmente defectuosa. Si bien idealmente la diferencia entre las dos opciones sería la que había escrito, lo que parece dar el visto bueno al caso 1, en la práctica es un poco más feo.

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

vs

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

me parece el caso 2 sea considerablemente más fácil de leer y más difícil de meter la pata, especialmente para los pequeños pedazos.

30

Definitivamente pondré el retorno dentro de la cerradura. De lo contrario, corre el riesgo de que otro hilo entre en el candado y modifique su variable antes de la declaración de devolución, por lo tanto, hacer que el llamador original reciba un valor diferente al esperado.

+3

Esto es correcto, un punto que los otros respondedores parecen faltar. Las muestras simples que han hecho pueden producir la misma IL, pero esto no es así para la mayoría de los escenarios de la vida real. –

+4

Estoy sorprendido de que las otras respuestas no hablen de esto –

+3

En esta muestra están hablando de usar una variable de pila para almacenar el valor de retorno, es decir, solo la declaración de devolución fuera del bloqueo y, por supuesto, la declaración de la variable. Otro hilo debería tener otra pila y por lo tanto no podría hacer ningún daño, ¿verdad? –

1

Por lo que vale, el documentation on MSDN tiene un ejemplo de cómo regresar desde el interior de la cerradura. De las otras respuestas aquí, parece ser bastante similar IL pero, para mí, parece más seguro regresar desde dentro del bloqueo porque entonces no corre el riesgo de que una variable de retorno sea sobrescrita por otro hilo.

0

lock() return <expression> declaraciones Siempre:

1) Entre bloquear

2) hace) tienda local (thread-safe para el valor del tipo especificado,

3) llena la tienda con el valor devuelto por <expression>,

4) de bloqueo de la salida

5) volver a la tienda.

Significa que el valor, devuelto de la instrucción de bloqueo, siempre "cocinado" antes del retorno.

No se preocupe por lock() return, no escuchar a nadie aquí))

Cuestiones relacionadas