web-dev-qa-db-fra.com

Une déclaration de retour doit-elle être à l'intérieur ou à l'extérieur d'un verrou?

Je viens de me rendre compte que, quelque part dans mon code, l'instruction de retour se trouve à l'intérieur du verrou et parfois à l'extérieur. Lequel est le meilleur?

1)

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

2)

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

}

Lequel devrais-je utiliser?

124

Essentiellement, cela simplifie toujours le code. Le point de sortie unique est un idéal sympa, mais je ne voudrais pas plier le code pour le réaliser ... Et si l’alternative consiste à déclarer une variable locale (en dehors de la serrure), en l’initialisant (à puis en le retournant (à l'extérieur de la serrure), alors je dirais qu'un simple "retour foo" à l'intérieur de la serrure est beaucoup plus simple.

Pour montrer la différence en IL, imaginons:

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

(notez que je soutiendrais volontiers que ReturnInside est un peu plus simple et plus propre de C #)

Et regardez le IL (mode de libération, 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
}

Ainsi, au niveau IL, ils sont [donner ou prendre des noms] identiques (j'ai appris quelque chose ;-p) . En tant que telle, la seule comparaison raisonnable est la loi (hautement subjective) du style de codage local ... ReturnInside pour la simplicité, mais je ne serais pas excité non plus.

170
Marc Gravell

Cela ne fait aucune différence. ils sont tous les deux traduits dans le même sens par le compilateur.

Pour clarifier, l'un ou l'autre est traduit efficacement en quelque chose avec la sémantique suivante:

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

return myData;
37
Greg Beech

Je voudrais certainement mettre le retour à l'intérieur de la serrure. Sinon, vous risquez qu'un autre thread entre le verrou et modifie votre variable avant l'instruction de retour, ce qui obligera donc l'appelant initial à recevoir une valeur différente de celle attendue.

30
Ricardo Villamil

Si vous pensez que le verrou à l'extérieur a l'air meilleur, mais faites attention si vous changez le code en:

return f(...)

Si f() doit être appelé alors que le verrou est maintenu, il doit évidemment se trouver à l'intérieur du verrou.

5
Rob Walker

Ça dépend, 

Je vais aller à contre-courant ici. Je retournerais généralement à l'intérieur de la serrure.

En général, la variable mydata est une variable locale. J'aime déclarer des variables locales pendant que je les initialise. J'ai rarement les données pour initialiser ma valeur de retour en dehors de mon verrou.

Donc, votre comparaison est réellement imparfaite. Même si, idéalement, la différence entre les deux options serait celle que vous aviez décrite, ce qui semble donner l’honneur à l’affaire 1, en pratique, c’est un peu plus laid.

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

vs.

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

Je trouve que le cas 2 est considérablement plus facile à lire et plus difficile à bousiller, en particulier pour de courts extraits.

4
Edward KMETT

Pour ce que cela vaut, la documentation sur MSDN présente un exemple de retour depuis l’intérieur du verrou. Dans les autres réponses, il semble que ce soit assez similaire mais, à mon avis, il semble plus sûr de revenir de l'intérieur du verrou car vous ne courez pas le risque qu'une variable de retour soit écrasée par un autre thread.

1
greyseal96

Pour faciliter la lecture du code par les autres développeurs, je suggérerais la première alternative.

1
Adam Asham

Dehors, l'air plus propre.

0
Ovidiu Pacurar

Les instructions lock() return <expression> toujours:

1) entrer serrure

2) crée un magasin local (thread-safe) pour la valeur du type spécifié,

3) remplit le magasin avec la valeur renvoyée par <expression>,

4) serrure de sortie

5) retourner le magasin.

Cela signifie que la valeur, renvoyée par l'instruction lock, est toujours "cuite" avant le retour.

Ne vous inquiétez pas pour lock() return, n'écoutez personne ici))

0
mshakurov