Ich habe eine Reihe von Eigenschaften, für die ich Lese- / Schreibsperren verwenden werde. Ich kann sie entweder mit einer try finally
oder einer using
Klausel implementieren .
In der try finally
würde ich das Schloss vor dem erwerben try
und in der freigeben finally
. In der using
Klausel würde ich eine Klasse erstellen, die die Sperre in ihrem Konstruktor abruft und in ihrer Dispose-Methode freigibt.
Ich verwende Lese- / Schreibsperren an vielen Stellen, daher habe ich nach Wegen gesucht, die prägnanter sind als try finally
. Ich bin daran interessiert, einige Ideen zu hören, warum ein Weg möglicherweise nicht empfohlen wird oder warum einer besser ist als der andere.
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();
}
}
c#
.net
multithreading
using-statement
Jeremy
quelle
quelle
Antworten:
Von MSDN mit Anweisung (C # -Referenz)
{ Font font1 = new Font("Arial", 10.0f); try { byte charset = font1.GdiCharSet; } finally { if (font1 != null) ((IDisposable)font1).Dispose(); } }
Im Grunde ist es der gleiche Code, aber mit einer schönen automatischen Nullprüfung und einem zusätzlichen Bereich für Ihre Variable . In der Dokumentation heißt es außerdem, dass "die korrekte Verwendung des IDisposable-Objekts sichergestellt wird", sodass Sie in Zukunft möglicherweise noch bessere Framework-Unterstützung für unklare Fälle erhalten.
Fahren Sie also mit Option 2 fort.
Es ist auch ein Plus, die Variable in einem Bereich zu haben , der sofort endet, nachdem sie nicht mehr benötigt wird.
quelle
using
über diesen Fall nach? dafür ist es nicht dausing
.try/catch
da es die einzige Möglichkeit ist, übertry/catch/finally
Block zu handhaben . hoffte,using
könnte auch damit umgehentry/finally
, das ist deine einzige Option in diesem Fall, denke ich. IMO denke ich jedoch, dass es in solchen Fällen immer ein Objekt / einen Code geben sollte, der für die Aufrechterhaltung der Lebensdauer des Objekts verantwortlich ist (wobei Dispose () immer aufgerufen werden sollte). Wenn eine Klasse nur die Instanziierung übernimmt und sich jemand anderes daran erinnern muss Um es zu entsorgen, riecht es dort ein bisschen. Ich bin mir nicht sicher, wie man das auf Sprachebene hinzufügen würde.Ich bevorzuge definitiv die zweite Methode. Es ist am Verwendungsort prägnanter und weniger fehleranfällig.
Im ersten Fall muss jemand, der den Code bearbeitet, darauf achten, nichts zwischen dem Aufruf zum Erfassen der Sperre (Lesen | Schreiben) und dem 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 häufig aus, da die Möglichkeit von Konflikten angesichts der Zeit, in der die Sperre gehalten wird, vermutlich sehr gering ist (und das Erwerben einer Lese- / Schreibsperre ist eine teurere Operation als eine einfache Sperre).
quelle
Berücksichtigen Sie die Möglichkeit, dass beide Lösungen schlecht sind, weil sie Ausnahmen maskieren .
Ein
try
ohne eincatch
sollte offensichtlich eine schlechte Idee sein; Siehe MSDN, warum dieusing
Aussage ebenfalls gefährlich ist.Beachten Sie auch, dass Microsoft jetzt ReaderWriterLockSlim anstelle von ReaderWriterLock empfiehlt.
Beachten Sie schließlich, 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 und saubere Lösung ist ein gutes Ziel. Wenn Sie jedoch nicht einfach verwenden können
lock(this){return mydateetc;}
, können Sie den Ansatz überdenken. mit mehr Infos bin ich mir sicher, dass Stack Overflow helfen kann ;-)quelle
Ich persönlich verwende die C # -Anweisung "using" so oft wie möglich, aber es gibt einige spezifische Dinge, die ich damit mache, um die genannten potenziellen 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 Anweisung "using" in eine Methode und die Verwendung der Objekte in eine andere Methode mit einem Block "try" / "catch" aufteile. Ich kann mehrere "using" -Anweisungen wie diese für verwandte Objekte verschachteln (manchmal gehe ich drei oder vier tief in meinen Produktionscode ein).
In meinen
Dispose()
Methoden für diese benutzerdefiniertenIDisposable
Klassen fange ich Ausnahmen (aber KEINE Fehler) ab und protokolliere sie (mit Log4net). Ich bin noch nie auf eine Situation gestoßen, in der eine dieser Ausnahmen möglicherweise meine Verarbeitung beeinträchtigen könnte. Die potenziellen Fehler können sich wie üblich auf dem Aufrufstapel ausbreiten und die Verarbeitung normalerweise mit einer entsprechenden protokollierten Nachricht (Fehler- und Stapelverfolgung) beenden.Wenn ich irgendwie auf eine Situation stoßen würde, in der eine signifikante Ausnahme auftreten könnte
Dispose()
, würde ich diese Situation neu gestalten. Ehrlich gesagt bezweifle ich, dass das jemals passieren wird.Der Umfang und die Bereinigungsvorteile der "Verwendung" machen es zu einer meiner beliebtesten C # -Funktionen. Übrigens arbeite ich in Java, C # und Python als meine Hauptsprachen, wobei viele andere hier und da hinzukommen, und "Verwenden" ist eine meiner beliebtesten Sprachfunktionen, da es ein praktisches, alltägliches Arbeitstier ist .
quelle
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 Ihren beiden Optionen ist die zweite Option die sauberste und leichter zu verstehende, was vor sich geht.
quelle
"Bündel von Eigenschaften" und Sperren auf der Ebene der Getter und Setter von Eigenschaften sehen falsch aus. Ihre Verriegelung ist viel zu feinkörnig. Bei den meisten typischen Objektverwendungen möchten Sie sicherstellen, dass Sie eine Sperre erworben haben, um gleichzeitig auf mehrere Eigenschaften zuzugreifen . Ihr spezieller Fall mag anders sein, aber ich bezweifle es irgendwie.
Wenn Sie die Sperre erwerben, wenn Sie auf das Objekt anstatt auf die Eigenschaft zugreifen, wird die Menge an Sperrcode, die Sie schreiben müssen, erheblich reduziert.
quelle
DRY sagt: zweite Lösung. Die erste Lösung dupliziert die Logik der Verwendung einer Sperre, die zweite nicht.
quelle
Try / Catch-Blöcke dienen im Allgemeinen der Ausnahmebehandlung, während Blöcke verwendet werden, um sicherzustellen, dass das Objekt entsorgt wird.
Für die Lese- / Schreibsperre ist ein Versuch / Fang möglicherweise am nützlichsten, aber Sie können auch beide verwenden, wie folgt:
using (obj) { try { } catch { } }
Damit können Sie implizit Ihre IDisposable-Schnittstelle aufrufen und die Ausnahmebehandlung präzise gestalten.
quelle
Ich denke, Methode 2 wäre besser.
quelle
Obwohl ich vielen der obigen Kommentare zustimme, einschließlich der Granularität der Sperre und der Behandlung fragwürdiger Ausnahmen, handelt es sich bei der Frage um einen Ansatz. Lassen Sie mich einen wichtigen Grund nennen, warum ich es vorziehe, das try {} finally-Modell zu verwenden ... Abstraktion.
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. Dies bedeutet, dass das Objekt, mit dem ich mich befasse, vom Typ ILock ist und mit dem ein Sperrbereich erstellt werden kann. Warum ist das wichtig?
Wir beschäftigen uns mit vielen verschiedenen Verriegelungsmechanismen und Verhaltensweisen. Ihr Sperrobjekt hat möglicherweise ein bestimmtes Zeitlimit, das verwendet wird. Ihre Sperrimplementierung kann eine Monitorsperre, eine Lesersperre, eine Schreibsperre oder eine Drehsperre sein. Aus Sicht des Anrufers ist dies jedoch alles irrelevant. Es ist ihnen wichtig, dass der Vertrag zum Sperren der Ressource eingehalten wird und dass die Sperre dies in einer Weise tut, die mit der 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 Schlossmechanik vor dem Anrufer zu verstecken. FWIW, ich habe den Overhead der Verwendungstechnik im Vergleich zum Try-finally gemessen, und der Overhead für die Zuweisung des Einwegobjekts liegt zwischen 2-3% Leistungsaufwand.
quelle
Ich bin überrascht, dass niemand vorgeschlagen hat, den Try-finally in anonymen Funktionen zu kapseln. Genau wie bei der Instanziierung und Entsorgung von Klassen mit der using-Anweisung bleibt die Sperre an einem Ort. Ich bevorzuge dies selbst nur, weil ich lieber das Wort "endlich" als das Wort "Entsorgen" lesen möchte, wenn ich darüber nachdenke, ein Schloss freizugeben.
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(); } } }
quelle
SoftwareJedi, ich habe kein Konto, daher kann ich meine Antworten nicht bearbeiten.
In jedem Fall war die vorherige Version nicht wirklich für den allgemeinen Gebrauch geeignet, da für die Lesesperre immer ein Rückgabewert erforderlich war. 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(); } } }
quelle
Im Folgenden werden Erweiterungsmethoden für die ReaderWriterLockSlim-Klasse 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() { /// <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(); } }
quelle
Um die Lösungen vergleichbar zu machen, würden Sie in Ihrem ersten Beispiel auch dort implementieren
IDisposable
. Dann würden SieDispose()
vomfinally
Block aus anrufen , anstatt die Sperre direkt aufzuheben.Dann wären Sie in Bezug auf die Implementierung von "Äpfeln zu Äpfeln" (und MSIL) (MSIL ist für beide Lösungen gleich). Es ist wahrscheinlich immer noch eine gute Idee, sie zu verwenden,
using
da der Umfang erweitert wurde und das Framework die ordnungsgemäße Verwendung sicherstelltIDisposable
(letzteres ist weniger vorteilhaft, wenn Sie sichIDisposable
selbst implementieren ).quelle
Wie dumm von mir. Es gibt eine Möglichkeit, dies noch einfacher zu machen, indem die gesperrten Methoden Teil jeder Instanz werden (anstelle von statischen wie in meinem vorherigen Beitrag). Jetzt bevorzuge ich das wirklich, weil es nicht nötig ist, "rwlMyLock_m" an eine andere Klasse oder Methode weiterzugeben.
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(); } } }
quelle