it-swarm.com.de

Sollte sich eine return-Anweisung innerhalb oder außerhalb einer Sperre befinden?

Ich habe gerade realisiert, dass ich irgendwo in meinem Code die return-Anweisung innerhalb des Schlosses und irgendwo außerhalb habe. Welches ist das beste?

1)

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

2)

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

}

Welches sollte ich verwenden?

124

Im Wesentlichen, was den Code einfacher macht. Single Point of Exit ist ein nettes Ideal, aber ich würde den Code nicht aus der Form biegen, nur um ihn zu erreichen ... Und wenn die Alternative eine lokale Variable (außerhalb des Schlosses) deklariert, sie initialisiert (innerhalb des Schlosses) und dann zurückschicken (außerhalb des Schlosses), dann würde ich sagen, dass ein einfaches "return foo" im Schloss viel einfacher ist.

Um den Unterschied in der IL zu zeigen, können Sie folgenden Code eingeben:

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

(Beachten Sie, dass ich gerne behaupten würde, dass ReturnInside ein einfacheres/saubereres Bit von C # ist.)

Und schau dir die IL an (Freigabemodus usw.):

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

Auf der IL-Ebene sind sie also [geben oder nehmen einige Namen] identisch (ich habe etwas gelernt ;-p) ..__ Der einzige sinnvolle Vergleich ist das (höchst subjektive) Gesetz des lokalen Kodierstils ..., das ich bevorzuge ReturnInside der Einfachheit halber, aber ich würde mich auch nicht aufregen.

170
Marc Gravell

Es macht keinen Unterschied; Sie werden beide vom Compiler in dieselbe Sache übersetzt.

Um dies zu verdeutlichen, wird beides effektiv mit der folgenden Semantik übersetzt:

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

return myData;
37
Greg Beech

Ich würde die Rückkehr definitiv in das Schloss stecken. Andernfalls riskieren Sie, dass ein anderer Thread die Sperre betritt und Ihre Variable vor der return-Anweisung ändert, sodass der ursprüngliche Aufrufer einen anderen Wert erhält als erwartet.

30

Wenn Sie denken, dass das Schloss von außen besser aussieht, seien Sie vorsichtig, wenn Sie am Ende den Code ändern:

return f(...)

Wenn f() bei gehaltener Sperre aufgerufen werden muss, muss es sich offensichtlich innerhalb der Sperre befinden, da ein solches Zurückhalten innerhalb der Sperre für Konsistenz sinnvoll ist.

5
Rob Walker

Es hängt davon ab, ob, 

Ich werde hier gegen den Strich gehen. Ich würde in der Regel in das Schloss zurückkehren.

Normalerweise ist die Variable mydata eine lokale Variable. Ich mag es, lokale Variablen zu deklarieren, während ich sie initialisiere. Ich habe selten die Daten, um meinen Rückgabewert außerhalb meines Schlosses zu initialisieren.

Ihr Vergleich ist also tatsächlich fehlerhaft. Im Idealfall wäre der Unterschied zwischen den beiden Optionen der von Ihnen geschriebene, der den ersten Blick auf Fall 1 zu geben scheint, in der Praxis ist er etwas hässlicher.

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

vs.

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

Ich finde, dass Fall 2 wesentlich lesbarer und schwieriger zu machen ist, insbesondere für kurze Ausschnitte.

4
Edward KMETT

Die Dokumentation auf MSDN hat ein Beispiel für die Rückkehr aus dem Inneren des Schlosses. Von den anderen Antworten hier aus scheint es ziemlich ähnlich zu sein, aber für mich scheint es sicherer zu sein, aus dem Schloss zurückzukehren, da dann nicht die Gefahr besteht, dass eine Rückgabevariable von einem anderen Thread überschrieben wird.

1
greyseal96

Um anderen Entwicklern das Lesen des Codes zu erleichtern, würde ich die erste Alternative vorschlagen.

1
Adam Asham

Draußen sieht es sauberer aus.

0
Ovidiu Pacurar

lock() return <expression>-Anweisungen immer:

1) Sperre eingeben

2) macht lokalen (Thread-sicheren) Speicher für den Wert des angegebenen Typs,

3) füllt das Geschäft mit dem von <expression> zurückgegebenen Wert,

4) Schließung verlassen

5) den Laden zurücksenden.

Dies bedeutet, dass der Wert, der von der Sperranweisung zurückgegeben wird, vor der Rückgabe immer "gekocht" wird.

Mach dir keine Sorgen über lock() return, höre hier niemandem zu)))

0
mshakurov