‘Using’ Aussage vs ‘endlich versuchen’

Ich habe eine Reihe von Eigenschaften, mit denen ich Lese- / Schreibsperren verwenden werde. Ich kann sie entweder mit einem try finally oder einer using Klausel implementieren.

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

Ich benutze Lese- / Schreib-Sperren an vielen Orten, also 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(); } } 

Von MSDN mithilfe von statement (C # -Referenz)

Die using-statement stellt sicher, dass Dispose aufgerufen wird, auch wenn eine Ausnahme auftritt, 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-statement vom Compiler übersetzt. Das Codebeispiel wird zu der Kompilierungszeit zuvor in dem folgenden Code erweitert (beachten Sie die zusätzlichen geschweiften Klammern, um den begrenzten Bereich für das Objekt zu erstellen):

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

Im Grunde ist es derselbe Code, aber mit einem netten automatischen Null-Check und einem zusätzlichen 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 zu haben , der sofort endet, nachdem sie nicht mehr benötigt wird, ist ebenfalls ein Pluspunkt.

Ich bevorzuge definitiv die zweite Methode. Es ist prägnanter am Ort der Verwendung und weniger errorsanfä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 eines Konflikts vermutlich sehr gering ist, wenn die Sperre gehalten wird und das Erlangen einer Lese- / Schreibsperre ist eine teurere Operation als eine einfache Sperre).

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

Ein try ohne einen catch sollte offensichtlich eine schlechte Idee sein; In MSDN finden Sie, warum die using statement ebenfalls gefährlich ist.

Hinweis: Microsoft empfiehlt jetzt ReaderWriterLockSlim anstelle von ReaderWriterLock.

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 unter der Annahme, dass Sie nicht einfach lock(this){return mydateetc;} , könnten Sie den Ansatz überdenken; mit mehr Infos bin ich sicher Stack Overflow kann helfen 😉

Ich benutze persönlich die C # “using” statement so oft wie möglich, aber es gibt ein paar spezifische Dinge, die ich zusammen mit ihm tun, um die möglichen Probleme zu vermeiden. Um zu veranschaulichen:

 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” -statement in eine Methode und die Verwendung des Objekts (der Objekte) in eine andere Methode mit einem “try” / “catch” -Block zerlege. Ich kann mehrere “using” -statementen 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 classn IDisposable ich Ausnahmen (aber keine Fehler) und protokolliere sie (mithilfe von 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 auf eine Situation gestoßen wäre, in der während Dispose() eine signifikante Ausnahme auftreten könnte, würde ich für diese Situation ein Redesign durchführen. Ehrlich gesagt bezweifle ich, dass das jemals passieren wird.

In der Zwischenzeit machen die Vorteile des “Nutzens” beim Scoping und Cleanup zu einer meiner liebsten 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 .

Ich mag die 3. Option

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

Von den zwei Optionen ist die zweite Option die sauberste und leichter zu verstehen, was vor sich geht.

“Bunch of Properties” 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 mehrere Eigenschaften gleichzeitig erworben haben. Ihr spezifischer Fall könnte anders sein, aber ich bezweifle es.

Wie auch immer, die Erfassung der Sperre beim Zugriff auf das Objekt anstelle der Eigenschaft wird die Menge an Sperrcode, die Sie schreiben müssen, erheblich reduzieren.

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

Try / Catch-Blöcke dienen in der Regel zur Ausnahmebehandlung, während mithilfe von Blöcken sichergestellt wird, dass das Objekt entsorgt wird.

Für die Lese- / Schreibsperre könnte ein try / catch am nützlichsten sein, aber Sie könnten auch beide verwenden, wie zum Beispiel:

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

Damit können Sie Ihre IDisposable-Schnittstelle implizit aufrufen und die Ausnahmebehandlung prägnant gestalten.

Ich denke, Methode 2 wäre besser.

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

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

Ich habe ein Modell, das Ihrem sehr ähnlich ist, mit einer Ausnahme. 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 dein Modell, aber ich würde in Betracht ziehen, die Sperrmechanik vor dem 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% performances-Overhead haben.

Ich bin überrascht, dass niemand vorgeschlagen hat, die Versuche in anonymen functionen zu versuchen. Genau wie die Technik, classn mit der using-statement 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(); } } } 

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 Folgendes:

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

Um in Ihrem ersten Beispiel die Lösungen vergleichbar zu machen, würden Sie “IDisposable” auch dort implementieren und “Dispose” aus dem Block “Finally” aufrufen, anstatt die Sperre direkt freizugeben. 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).

Im Folgenden werden Erweiterungsmethoden für die ReaderWriterLockSlim-class erstellt, mit denen Sie Folgendes ausführen können:

 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() { ///  /// Allows you to enter and exit a read lock with a using statement ///  /// The lock /// A new object that will ExitReadLock on dispose 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()); } ///  /// Allows you to enter and exit a write lock with a using statement ///  /// The lock /// A new object that will ExitWriteLock on dispose 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()); } } ///  /// Calls the finished action on dispose. For use with a using statement. ///  public class OnDispose : IDisposable { Action _finished; public OnDispose(Action finished) { _finished = finished; } public void Dispose() { _finished(); } } 

Wie dumm von mir. 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 class 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(); } } }