c# - sirve - ¿Debería una declaración de devolución estar dentro o fuera de una cerradura?
para que sirve mutex en c# (9)
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?
1)
void example()
{
lock (mutex)
{
//...
}
return myData;
}
2)
void example()
{
lock (mutex)
{
//...
return myData;
}
}
¿Cuál debería usar?
Definitivamente pondría la devolución 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.
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.
Entonces 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 ...;
}
}
Encuentro que el caso 2 es considerablemente más fácil de leer y más difícil de estropear, especialmente para fragmentos cortos.
El exterior se ve más limpio.
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 el 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;
}
}
(Nótese que me alegraría decir que ReturnInside
es una parte más simple / limpia de C #)
Y mira 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
}
Entonces en el nivel IL son [dar o tomar algunos nombres] idénticos (aprendí algo ;-p). Como tal, la única comparación sensata es la ley (altamente subjetiva) del estilo de codificación local ... prefiero ReturnInside
por simplicidad, pero tampoco me entusiasmaría.
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 siguiente semántica:
T myData;
Monitor.Enter(mutex)
try
{
myData= // something
}
finally
{
Monitor.Exit(mutex);
}
return myData;
Para facilitar que otros desarrolladores lean el código, sugeriría la primera alternativa.
Por lo que vale, la documentación en MSDN tiene un ejemplo de cómo regresar desde el interior del bloqueo. 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.
Si piensas que el candado exterior se ve mejor, ten cuidado si terminas cambiando el código a:
return f(...)
Si se necesita llamar a f () con la cerradura retenida, obviamente debe estar dentro de la cerradura, ya que mantener las devoluciones dentro de la cerradura tiene sentido.
lock() return <expression>
declaraciones lock() return <expression>
siempre:
1) ingrese el candado
2) almacena localmente (hilo-seguro) para el valor del tipo especificado,
3) llena la tienda con el valor devuelto por <expression>
,
4) cerradura de salida
5) devuelve la tienda.
Significa que el valor devuelto por la instrucción de bloqueo siempre se "cocina" antes de devolver.
No te preocupes por el lock() return
, no escuches a nadie aquí))