2008-11-10 6 views
58

Ich habe eine Reihe von Eigenschaften, die ich Lese-/Schreib-Sperren verwenden werde. Ich kann sie entweder mit einer try finally oder einer using Klausel implementieren.'using' statement vs 'try finally'

In der try finally würde ich das Schloss vor der try erwerben und in der finally freigeben. In der using-Klausel würde ich eine Klasse erstellen, die die Sperre in ihrem Konstruktor erwirbt und in ihrer Dispose-Methode freigibt.

Ich benutze Lese/Schreib-Sperren an vielen Orten, so habe ich nach Wegen gesucht, die prägnanter sein könnten als try finally. Ich bin daran interessiert, einige Ideen zu hören, warum ein Weg möglicherweise nicht empfohlen wird, oder warum einer besser sein könnte als ein anderer.

Methode 1 (try finally):

static ReaderWriterLock rwlMyLock_m = new ReaderWriterLock(); 
private DateTime dtMyDateTime_m 
public DateTime MyDateTime 
{ 
    get 
    { 
     rwlMyLock_m .AcquireReaderLock(0); 
     try 
     { 
      return dtMyDateTime_m 
     } 
     finally 
     { 
      rwlMyLock_m .ReleaseReaderLock(); 
     } 
    } 
    set 
    { 
     rwlMyLock_m .AcquireWriterLock(0); 
     try 
     { 
      dtMyDateTime_m = value; 
     } 
     finally 
     { 
      rwlMyLock_m .ReleaseWriterLock(); 
     } 
    } 
} 

Methode 2:

static ReaderWriterLock rwlMyLock_m = new ReaderWriterLock(); 
private DateTime dtMyDateTime_m 
public DateTime MyDateTime 
{ 
    get 
    { 
     using (new ReadLock(rwlMyLock_m)) 
     { 
      return dtMyDateTime_m; 
     } 
    } 
    set 
    { 
     using (new WriteLock(rwlMyLock_m)) 
     { 
      dtMyDateTime_m = value; 
     } 
    } 
} 

public class ReadLock : IDisposable 
{ 
    private ReaderWriterLock rwl; 
    public ReadLock(ReaderWriterLock rwl) 
    { 
     this.rwl = rwl; 
     rwl.AcquireReaderLock(0); 
    } 

    public void Dispose() 
    { 
     rwl.ReleaseReaderLock(); 
    } 
} 

public class WriteLock : IDisposable 
{ 
    private ReaderWriterLock rwl; 
    public WriteLock(ReaderWriterLock rwl) 
    { 
     this.rwl = rwl; 
     rwl.AcquireWriterLock(0); 
    } 

    public void Dispose() 
    { 
     rwl.ReleaseWriterLock(); 
    } 
} 

Antwort

73

Von MSDN, using Statement (C# Reference)

Die using-Anweisung stellt sicher, dass Entsorgen auch wenn eine Ausnahme auftritt, aufgerufen wird, während Sie Methoden für das Objekt aufrufen. Sie können dasselbe Ergebnis erzielen, indem Sie das Objekt in einen try-Block setzen und dann Dispose in einem finally-Block aufrufen. Genau so wird die using-Anweisung vom Compiler übersetzt. Das Codebeispiel erweitert früher den folgenden Code bei der Kompilierung (beachten Sie die zusätzliche geschweiften Klammern den begrenzten Anwendungsbereich für das Objekt erstellen):

{ 
    Font font1 = new Font("Arial", 10.0f); 
    try 
    { 
    byte charset = font1.GdiCharSet; 
    } 
    finally 
    { 
    if (font1 != null) 
     ((IDisposable)font1).Dispose(); 
    } 
} 

Also im Grunde es ist der gleiche Code aber mit einem schönen automatische Null-Checks und ein zusätzlicher Bereich für Ihre Variable. Die Dokumentation besagt auch, dass es "die korrekte Verwendung von IDisposable Object gewährleistet", so dass Sie auch in Zukunft noch bessere Framework-Unterstützung für alle obskuren Fälle erhalten können.

Also gehen Sie mit Option 2.

Die Variable in einem Bereich, die sofort endet, nachdem es nicht mehr benötigt wird, ist auch ein Plus.

+0

was ist am besten, um eine Ressource freizugeben, die nicht instanziiert werden kann, indem man statement verwendet oder wiederverwendet wird oder als out param übergeben wird? Try/catch !!! – bjan

+2

@bjan nun, warum denkst du überhaupt über 'using' in diesem Fall? Das ist nicht das, wofür 'using' verwendet wird. – chakrit

+1

Deshalb erwähne ich auch "try/catch", da es die einzige Möglichkeit ist, über 'try/catch/finally' zu blocken. hoffte 'using' konnte das auch handhaben – bjan

11

Ich ziehe es auf jeden Fall die zweite Methode. Es ist prägnanter am Ort der Verwendung und weniger fehleranfällig.

Im ersten Fall muss jemand, der den Code editiert, darauf achten, nichts zwischen den Aufruf (Lesen | Schreiben) Lock und den Versuch einzufügen.

(Die Verwendung einer Lese-/Schreibsperre für einzelne Eigenschaften wie diese ist jedoch in der Regel übertrieben. Sie werden am besten auf einer viel höheren Ebene angewendet. Eine einfache Sperre reicht hier oft aus, da die Wahrscheinlichkeit der Konkurrenz vermutlich sehr gering ist die Sperre wird gehalten, und das Erlangen einer Lese-/Schreibsperre ist eine teurere Operation als eine einfache Sperre).

+0

Wie wäre es ausfallsicher zu sein? Ich weiß, ein Versuch wird schließlich immer den finalen Block abfeuern, gibt es irgendeinen Weg, den die Dispose nicht bekommen wird? – Jeremy

+1

Nein, die using-Anweisung ist im Wesentlichen syntaktischer Zucker für das try/finally-Muster. –

+0

Das verwendete Modell stellt sicher, dass das Objekt immer entsorgt wird. –

0

Ich denke, Methode 2 wäre besser.

  • Einfacher und besser lesbarer Code in Ihren Eigenschaften.
  • Weniger fehleranfällig, da der Sperrcode nicht mehrmals neu geschrieben werden muss.
3

DRY sagt: zweite Lösung. Die erste Lösung dupliziert die Logik der Verwendung einer Sperre, während die zweite nicht.

1

Try/Catch-Blöcke sind im Allgemeinen für die Ausnahmebehandlung, während die Verwendung von Blöcken verwendet wird, um sicherzustellen, dass das Objekt entsorgt wird.

Für die Lese-/Schreibsperre eine try/catch könnte das nützlich sein, aber man könnte auch beide verwenden, etwa so:

using (obj) 
{ 
    try { } 
    catch { } 
} 

, so dass Sie implizit Ihre IDisposable-Schnittstelle sowie nennen Ausnahmebehandlung prägnant.

4

ich die dritte Option

private object _myDateTimeLock = new object(); 
private DateTime _myDateTime; 

public DateTime MyDateTime{ 
    get{ 
    lock(_myDateTimeLock){return _myDateTime;} 
    } 
    set{ 
    lock(_myDateTimeLock){_myDateTime = value;} 
    } 
} 

Ihrer beiden Optionen gefällt, ist die zweite Option die sauberste und leichter zu verstehen, was los ist.

+0

Lock-Anweisung funktioniert nicht auf die gleiche Weise wie ReaderWriterLock. – chakrit

+0

@chakrit: Nein, aber wenn Sie nicht wissen, dass es tatsächlich Sperrkonflikte gibt, sind sie wahrscheinlich leistungsfähiger. –

+0

Stimmt, aber Sie sollten immer zuerst für die Smlock-Sperre gehen. Die Frage des Subbys sagt nichts über Leistungsprobleme oder Anforderungen aus, die eine Sperre nicht zulassen. Daher gibt es keinen Grund zu versuchen und zu sein. Schließe es einfach und rocke es. – Will

8

Betrachten Sie die Möglichkeit, dass beide Lösungen schlecht sind, da sie Ausnahmen maskieren.

Eine try ohne eine catch sollte offensichtlich eine schlechte Idee sein; siehe MSDN dafür, warum die using Aussage ebenfalls gefährlich ist.

Hinweis: Microsoft empfiehlt jetzt anstelle von ReaderWriterLock ReaderWriterLockSlim.

Beachten Sie, dass die Microsoft-Beispiele zwei Try-Catch-Blöcke verwenden, um diese Probleme zu vermeiden, z.

try 
{ 
    try 
    { 
     //Reader-writer lock stuff 
    } 
    finally 
    { 
     //Release lock 
    } 
} 
catch(Exception ex) 
{ 
    //Do something with exception 
} 

Eine einfache, konsistente, saubere Lösung ist ein gutes Ziel, aber vorausgesetzt, Sie können nicht nur lock(this){return mydateetc;} verwenden, können Sie den Ansatz überdenken; mit mehr Informationen Ich bin sicher, dass Stack   Überlauf kann helfen ;-)

+1

Ein Versuch schließlich maskiert nicht unbedingt die Ausnahme. In meinem Beispiel wird die Sperre erworben, und wenn eine Ausnahme innerhalb des Bereichs ausgelöst wird, wird die Sperre aufgehoben, aber die Ausnahme wird immer noch eingeblendet. – Jeremy

+1

@ Jeremy: Wenn Ihr Block schließlich eine Ausnahme auslöst, wird die in Ihrem try-Block geworfene Ausnahme maskiert - das war der MSDN-Artikel, der das (gleiche) Problem mit der Syntax –

+3

war. Es wird die Ausnahme nicht maskieren. Ich ersetze die Ausnahme genauso wie Sie. –

5

Ich persönlich benutze die C# "verwenden" Anweisung so oft wie möglich, aber es gibt ein paar bestimmte Dinge, die ich zusammen mit ihm tun, um das Potenzial zu vermeiden Probleme erwähnt. Zur Veranschaulichung:

void doSomething() 
{ 
    using (CustomResource aResource = new CustomResource()) 
    { 
     using (CustomThingy aThingy = new CustomThingy(aResource)) 
     { 
      doSomething(aThingy); 
     } 
    } 
} 

void doSomething(CustomThingy theThingy) 
{ 
    try 
    { 
     // play with theThingy, which might result in exceptions 
    } 
    catch (SomeException aException) 
    { 
     // resolve aException somehow 
    } 
} 

Beachten Sie, dass ich die „using“ Anweisung in ein Verfahren und die Verwendung des Objekts (s) in einer anderen Methode mit einem „try“/„fängt“ Block trennen. Ich kann mehrere "using" -Anweisungen wie diese für verwandte Objekte verschachteln (ich gehe manchmal drei oder vier tief in meinen Produktionscode).

In meinen Dispose() Methoden für diese benutzerdefinierten IDisposable Klassen, erhalte ich Ausnahmen (aber keine Fehler) und protokolliere sie (mit Log4net). Ich bin nie auf eine Situation gestoßen, in der eine dieser Ausnahmen meine Verarbeitung beeinträchtigen könnte. Die potentiellen Fehler können sich wie üblich in dem Aufrufstapel ausbreiten und typischerweise die Verarbeitung mit einer entsprechenden Nachricht (der Fehler- und Stapelablaufverfolgung) protokollieren.

Wenn ich irgendwie eine Situation, in der eine signifikante Ausnahme während Dispose() auftreten könnte, würde ich für diese Situation neu gestalten. Ehrlich gesagt bezweifle ich, dass das jemals passieren wird.

In der Zwischenzeit machen die Scope und Cleanup-Vorteile von "verwenden" es zu einem meiner beliebtesten C# -Features.Übrigens arbeite ich in Java, C# und Python als meine primären Sprachen, mit vielen anderen hier und da, und "verwenden" ist eine meiner beliebtesten Sprachfunktionen rund um, weil es ein praktisches, alltägliches Arbeitspferd ist .

+0

Tipp, um den Code lesbar zu machen: Richten Sie die uses-Anweisungen aus und komprimieren Sie sie, indem Sie keine geschweiften Klammern haben, mit Ausnahme der inneren "Verwendung". –

4

"Bund der Eigenschaften" und Sperren auf der Eigenschaft Getter und Setter-Ebene sieht falsch aus. Ihre Verriegelung ist viel zu feinkörnig. Bei den meisten typischen Objektverwendungen sollten Sie sicherstellen, dass Sie eine Sperre für den Zugriff auf mehr als eine Eigenschaft zur gleichen Zeit erworben haben. Ihr spezifischer Fall könnte anders sein, aber ich bezweifle es.

Wie auch immer, das Erlangen der Sperre, wenn Sie auf das Objekt statt auf die Eigenschaft zugreifen, wird die Menge an Sperrcode, die Sie schreiben müssen, erheblich reduzieren.

+0

Ja, ich sehe definitiv Ihren Standpunkt. Die meisten meiner Eigenschaften sind eigentlich Bools, Ints, die ich nicht sperren müsste, weil sie atomar sein sollten. Es gibt einige datetime, string diejenigen, die ich sperren möchte. weil die Minderheit Schlösser benötigen würde, würde ich es am besten auf die Immobilie setzen – Jeremy

0

Während ich mit vielen der obigen Kommentare, einschließlich der Granularität der Sperre und der fragwürdigen Ausnahmebehandlung einverstanden bin, ist die Frage einer der Annäherung. Lassen Sie mich einen wichtigen Grund nennen, warum ich lieber über das try {} finally model ... abstrahieren möchte.

Ich habe ein Modell, das Ihrem mit einer Ausnahme sehr ähnlich ist. Ich habe eine Basisschnittstelle ILock definiert und darin eine Methode namens Acquire() bereitgestellt. Die Acquire() - Methode hat das IDisposable-Objekt zurückgegeben und bedeutet daher, dass das Objekt, mit dem ich es zu tun habe, vom Typ ILock ist, mit dem ein Sperrbereich ausgeführt werden kann. Warum ist das wichtig?

Wir beschäftigen uns mit vielen verschiedenen Schließmechanismen und Verhaltensweisen. Ihr Sperrobjekt hat möglicherweise ein bestimmtes Zeitlimit, das verwendet wird. Ihre Schlossimplementierung kann eine Monitorsperre, Lesersperre, Schreibsperre oder Drehsperre sein. Aus der Sicht des Anrufers ist das alles jedoch irrelevant. Was sie interessiert, ist, dass der Vertrag, die Ressource zu sperren, eingehalten wird und dass das Schloss dies in einer Weise tut, die mit seiner Implementierung übereinstimmt.

interface ILock { 
    IDisposable Acquire(); 
} 

class MonitorLock : ILock { 
    IDisposable Acquire() { ... acquire the lock for real ... } 
} 

Ich mag Ihr Modell, aber ich würde in Betracht ziehen, die Sperrmechanik vom Anrufer zu verbergen. FWIW, ich habe den Overhead der Benutzungs-Technik im Vergleich zu try-finally gemessen und der Overhead der Zuweisung des Wegwerf-Objekts wird zwischen 2-3% Leistungs-Overhead haben.

0

Ich bin überrascht, niemand hat vorgeschlagen, die Probe-endlich in anonymen Funktionen einzukapseln. Genau wie die Technik, Klassen mit der using-Anweisung zu instantiieren und zu disponieren, hält dies das Locking an einer Stelle. Ich bevorzuge das selbst nur, weil ich lieber das Wort "endlich" als das Wort "Dispose" lesen würde, wenn ich daran denke, ein Schloss zu lösen.

class StackOTest 
{ 
    private delegate DateTime ReadLockMethod(); 
    private delegate void WriteLockMethod(); 

    static ReaderWriterLock rwlMyLock_m = new ReaderWriterLock(); 
    private DateTime dtMyDateTime_m; 
    public DateTime MyDateTime 
    { 
     get 
     { 
      return ReadLockedMethod(
       rwlMyLock_m, 
       delegate() { return dtMyDateTime_m; } 
      ); 
     } 
     set 
     { 
      WriteLockedMethod(
       rwlMyLock_m, 
       delegate() { dtMyDateTime_m = value; } 
      ); 
     } 
    } 

    private static DateTime ReadLockedMethod(
     ReaderWriterLock rwl, 
     ReadLockMethod method 
    ) 
    { 
     rwl.AcquireReaderLock(0); 
     try 
     { 
      return method(); 
     } 
     finally 
     { 
      rwl.ReleaseReaderLock(); 
     } 
    } 

    private static void WriteLockedMethod(
     ReaderWriterLock rwl, 
     WriteLockMethod method 
    ) 
    { 
     rwl.AcquireWriterLock(0); 
     try 
     { 
      method(); 
     } 
     finally 
     { 
      rwl.ReleaseWriterLock(); 
     } 
    } 
} 
-1

Dumm mich. Es gibt eine Möglichkeit, dies noch einfacher zu machen, indem Sie die gesperrten Methoden Teil jeder Instanz machen (statt wie in meinem vorherigen Beitrag statisch). Jetzt bevorzuge ich das wirklich, weil es nicht nötig ist, `rwlMyLock_m 'an eine andere Klasse oder Methode zu übergeben.

class StackOTest 
{ 
    private delegate DateTime ReadLockMethod(); 
    private delegate void WriteLockMethod(); 

    static ReaderWriterLock rwlMyLock_m = new ReaderWriterLock(); 
    private DateTime dtMyDateTime_m; 
    public DateTime MyDateTime 
    { 
     get 
     { 
      return ReadLockedMethod(
       delegate() { return dtMyDateTime_m; } 
      ); 
     } 
     set 
     { 
      WriteLockedMethod(
       delegate() { dtMyDateTime_m = value; } 
      ); 
     } 
    } 

    private DateTime ReadLockedMethod(ReadLockMethod method) 
    { 
     rwlMyLock_m.AcquireReaderLock(0); 
     try 
     { 
      return method(); 
     } 
     finally 
     { 
      rwlMyLock_m.ReleaseReaderLock(); 
     } 
    } 

    private void WriteLockedMethod(WriteLockMethod method) 
    { 
     rwlMyLock_m.AcquireWriterLock(0); 
     try 
     { 
      method(); 
     } 
     finally 
     { 
      rwlMyLock_m.ReleaseWriterLock(); 
     } 
    } 
} 
+1

Bearbeiten Sie einfach Ihre andere Antwort, duplizieren Sie sie nicht. – TheSoftwareJedi

0

SoftwareJedi, ich habe kein Konto, daher kann ich meine Antworten nicht bearbeiten.

In jedem Fall war die vorherige Version nicht wirklich gut für allgemeine Zwecke, da die Lesesperre immer einen Rückgabewert benötigte. Dies behebt das:

class StackOTest 
{ 
    static ReaderWriterLock rwlMyLock_m = new ReaderWriterLock(); 
    private DateTime dtMyDateTime_m; 
    public DateTime MyDateTime 
    { 
     get 
     { 
      DateTime retval = default(DateTime); 
      ReadLockedMethod(
       delegate() { retval = dtMyDateTime_m; } 
      ); 
      return retval; 
     } 
     set 
     { 
      WriteLockedMethod(
       delegate() { dtMyDateTime_m = value; } 
      ); 
     } 
    } 

    private void ReadLockedMethod(Action method) 
    { 
     rwlMyLock_m.AcquireReaderLock(0); 
     try 
     { 
      method(); 
     } 
     finally 
     { 
      rwlMyLock_m.ReleaseReaderLock(); 
     } 
    } 

    private void WriteLockedMethod(Action method) 
    { 
     rwlMyLock_m.AcquireWriterLock(0); 
     try 
     { 
      method(); 
     } 
     finally 
     { 
      rwlMyLock_m.ReleaseWriterLock(); 
     } 
    } 
} 
0

Eigentlich in Ihrem ersten Beispiel auf die Lösungen vergleichbar zu machen, würden Sie auch dort auch „IDisposable“ implementieren und rufen „Entsorgen“ aus dem „Finally“ Block stattdessen die Sperre direkt die Freigabe . Dann würden Sie "Äpfel zu Äpfeln" Umsetzung (und MSIL) -Wise sein (MSIL wird für beide Lösungen gleich sein).Es ist immer noch eine gute Idee, "using" zu verwenden, da das Scoping hinzugefügt wurde und das Framework die korrekte Verwendung von "IDisposable" gewährleistet (letzteres ist weniger vorteilhaft, wenn Sie "IDisposabe" selbst implementieren).

0

Die folgende schafft Erweiterungsmethoden für die ReaderWriterLockSlim Klasse, die es Ihnen ermöglichen, die folgendes zu tun:

var rwlock = new ReaderWriterLockSlim(); 
using (var l = rwlock.ReadLock()) 
{ 
    // read data 
} 
using (var l = rwlock.WriteLock()) 
{ 
    // write data 
} 

Hier ist der Code:

static class ReaderWriterLockExtensions() { 
    /// <summary> 
    /// Allows you to enter and exit a read lock with a using statement 
    /// </summary> 
    /// <param name="readerWriterLockSlim">The lock</param> 
    /// <returns>A new object that will ExitReadLock on dispose</returns> 
    public static OnDispose ReadLock(this ReaderWriterLockSlim readerWriterLockSlim) 
    { 
     // Enter the read lock 
     readerWriterLockSlim.EnterReadLock(); 
     // Setup the ExitReadLock to be called at the end of the using block 
     return new OnDispose(() => readerWriterLockSlim.ExitReadLock()); 
    } 
    /// <summary> 
    /// Allows you to enter and exit a write lock with a using statement 
    /// </summary> 
    /// <param name="readerWriterLockSlim">The lock</param> 
    /// <returns>A new object that will ExitWriteLock on dispose</returns> 
    public static OnDispose WriteLock(this ReaderWriterLockSlim rwlock) 
    { 
     // Enter the write lock 
     rwlock.EnterWriteLock(); 
     // Setup the ExitWriteLock to be called at the end of the using block 
     return new OnDispose(() => rwlock.ExitWriteLock()); 
    } 
} 

/// <summary> 
/// Calls the finished action on dispose. For use with a using statement. 
/// </summary> 
public class OnDispose : IDisposable 
{ 
    Action _finished; 

    public OnDispose(Action finished) 
    { 
     _finished = finished; 
    } 

    public void Dispose() 
    { 
     _finished(); 
    } 
} 
Verwandte Themen