2008-11-05 4 views
111

Je viens de me rendre compte que dans un endroit de mon code j'ai la déclaration de retour dans la serrure et parfois dehors. Lequel est le meilleur?Une déclaration de retour doit-elle être à l'intérieur ou à l'extérieur d'un verrou?

1)

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

2)

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

} 

Lequel dois-je utiliser?

+0

Que diriez-vous de tir réflecteur et faire une comparaison IL ;-). –

+6

@Pop: fait - aucun n'est meilleur en IL - seul le style C# s'applique –

+1

Très intéressant, wow j'apprends quelque chose aujourd'hui! – Pokus

Répondre

155

Essentiellement, ce qui rend le code plus simple. Un seul point de sortie est un bel idéal, mais je ne plierais pas le code pour le réaliser ... Et si l'alternative est de déclarer une variable locale (en dehors du verrou), l'initialiser (à l'intérieur du verrou) et puis je le retourne (en dehors 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 de IL, permet de 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; 
    } 
} 

(notez que je dirais heureusement que ReturnInside est un simple/peu plus propre de C#)

Et regarde l'IL (mode de sortie, 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 
} 

donc, au niveau iL ils sont [donner ou prendre des noms identiques] (j'appris quelque chose ;-P). En tant que tel, la seule comparaison raisonnable est la loi (hautement subjective) du style de codage local ... Je préfère ReturnInside pour la simplicité, mais je ne serais pas excité à propos non plus.

+1

code –

+11

J'ai utilisé le (libre et excellent) réflecteur .NET de Red Gate (était: .NET Reflector de Lutz Roeder), mais ILDASM ferait aussi –

+1

L'un des aspects les plus puissants de Reflector est que vous pouvez réellement démonter IL à votre langue préférée (C#, VB, Delphi, MC++, Chrome, etc.) –

34

Cela ne fait aucune différence; ils sont tous les deux traduits à la même chose par le compilateur.

Pour clarifier les choses, soit est effectivement traduit à quelque chose avec la sémantique suivante:

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

return myData; 
+1

Eh bien, c'est vrai de l'essai/finalement - cependant, le retour en dehors du verrou nécessite encore des locaux supplémentaires qui ne peuvent pas être optimisés - et prend plus de code ... –

+2

Vous ne pouvez pas revenir d'un bloc try; il doit se terminer par un code d'opération ".leave". Donc le CIL émis devrait être le même dans les deux cas. –

+2

Vous avez raison - je viens de regarder l'IL (voir mise à jour post). J'ai appris quelque chose ;-p –

0

regarde à l'extérieur plus propre.

+0

Que se passe-t-il lorsque le thread en cours sort du verrou et que le thread suivant arrive en ligne, modifie la variable que vous renvoyez avant de la renvoyer? –

1

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

5

Si pense que le verrou regarde à l'extérieur mieux, mais soyez prudent si vous finissez par changer le code:

return f(...) 

Si f() doit être appelée maintenant le verrouillage alors il doit évidemment être à l'intérieur la serrure, en tant que telle garder des retours à l'intérieur de la serrure pour la cohérence est logique.

4

Cela dépend,

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

Habituellement, 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.

Votre comparaison est donc erronée. Tandis qu'idéalement la différence entre les deux options serait comme tu l'avais écrit, ce qui semble donner le clin d'oeil au cas 1, dans la pratique c'est un peu plus laide.

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

contre

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

je trouve le cas 2 d'être beaucoup plus facile à lire et plus difficile à bousiller, en particulier pour les courts extraits.

30

Je voudrais certainement mettre le retour à l'intérieur de la serrure. Sinon, vous risquez un autre thread d'entrer dans le verrou et de modifier votre variable avant l'instruction return, ce qui fait que l'appelant d'origine reçoit une valeur différente de celle attendue.

+3

C'est correct, un point que les autres intervenants semblent avoir manqué. Les échantillons simples qu'ils ont réalisés peuvent produire la même IL, mais ce n'est pas le cas pour la plupart des scénarios de la vie réelle. –

+4

Im surpris les autres réponses ne parlent pas de cela –

+3

Dans cet exemple, ils parlent d'utiliser une variable de pile pour stocker la valeur de retour, c'est-à-dire seulement l'instruction de retour en dehors du verrou et bien sûr la déclaration de variable. Un autre thread devrait avoir une autre pile et ne pourrait donc pas faire de mal, ai-je raison? –

1

Pour ce que ça vaut, le documentation on MSDN a un exemple de retour de l'intérieur de la serrure. D'après les autres réponses ici, il semble être assez similaire à IL mais, pour moi, il semble plus sûr de revenir de l'intérieur du verrou parce que vous ne courez pas le risque qu'une variable de retour soit écrasée par un autre thread.

0

lock() return <expression> déclarations toujours:

1) ENTRER verrouillage

2) fait magasin (thread-safe) pour la valeur du type spécifié,

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

4) de verrouillage de sortie

5) retourner le magasin.

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

Ne vous inquiétez pas lock() return, ne l'écoutez pas à tout le monde ici))

Questions connexes