Pregunta

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 debo usar?

¿Fue útil?

Solución

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

Para mostrar la diferencia en IL, permite codificar:

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 argumentaría que ReturnInside es un bit más simple / más limpio de C #)

Y mire 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 <=> por simplicidad, pero tampoco me entusiasmaría.

Otros consejos

No hace ninguna diferencia; ambos están traducidos a lo mismo por el compilador.

Para aclarar, cualquiera de los dos se traduce efectivamente a algo con la siguiente semántica:

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

return myData;

Definitivamente pondría el retorno dentro de la cerradura. De lo contrario, corre el riesgo de que otro hilo ingrese al bloqueo y modifique su variable antes de la declaración de devolución, por lo tanto, la persona que llama original recibe un valor diferente al esperado.

Si cree que la cerradura exterior se ve mejor, pero tenga cuidado si termina cambiando el código a:

return f(...)

Si es necesario invocar f () con el candado retenido, entonces obviamente debe estar dentro del candado, por lo que mantener los retornos dentro del candado para mantener la coherencia tiene sentido.

Depende,

Voy a ir contra la corriente aquí. En general, volvería dentro de la cerradura.

Por lo general, la variable mydata es una variable local. Soy aficionado a declarar variables locales mientras las inicializo. Raramente tengo los datos para inicializar mi valor de retorno 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 indicar el 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 ...;
    }
}

Creo que el caso 2 es considerablemente más fácil de leer y más difícil de arruinar, especialmente para fragmentos cortos.

Para facilitar a los demás desarrolladores la lectura del código, sugeriría la primera alternativa.

Para lo que vale, la documentación en MSDN tiene un ejemplo de regreso desde el interior de la cerradura. De las otras respuestas aquí, parece ser una IL bastante similar, pero, para mí, parece más seguro regresar desde el interior de la cerradura porque entonces no corre el riesgo de que una variable de retorno sea sobrescrita por otro hilo.

El exterior se ve más limpio.

lock() return <expression> declaraciones siempre:

1) ingresar bloqueo

2) crea una tienda local (segura para subprocesos) para el valor del tipo especificado,

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

4) bloqueo de salida

5) devolver la tienda.

Significa que el valor, devuelto por la declaración de bloqueo, siempre " cocinado " antes de regresar.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top