2008-10-04 9 views

Antwort

16

ich das gleiche dachte, aber in C# ;-p

using System; 
using System.Threading; 

class Program 
{ 
    static void Main() 
    { 
     ReaderWriterLockSlim sync = new ReaderWriterLockSlim(); 

     using (sync.Read()) 
     { 
      // etc  
     } 
    } 


} 
public static class ReaderWriterExt 
{ 
    sealed class ReadLockToken : IDisposable 
    { 
     private ReaderWriterLockSlim sync; 
     public ReadLockToken(ReaderWriterLockSlim sync) 
     { 
      this.sync = sync; 
      sync.EnterReadLock(); 
     } 
     public void Dispose() 
     { 
      if (sync != null) 
      { 
       sync.ExitReadLock(); 
       sync = null; 
      } 
     } 
    } 
    public static IDisposable Read(this ReaderWriterLockSlim obj) 
    { 
     return new ReadLockToken(obj); 
    } 
} 
+0

+1 großes Schnipsel! –

+1

Komisch, das hatte ich längst vergessen und nur zufällig taucht es in meinen Reputationspunkten auf, genauso wie ich davon profitieren könnte. –

0

Ich endete damit, aber ich bin immer noch offen für bessere Möglichkeiten oder Mängel in meinem Design.

Using m_Lock.ReadSection 
    Return m_List.Count 
End Using 

Dies nutzt diese Erweiterung Methode/Klasse:

<Extension()> Public Function ReadSection(ByVal lock As ReaderWriterLockSlim) As ReadWrapper 
    Return New ReadWrapper(lock) 
End Function 


Public NotInheritable Class ReadWrapper 
    Implements IDisposable 

    Private m_Lock As ReaderWriterLockSlim 
    Public Sub New(ByVal lock As ReaderWriterLockSlim) 
     m_Lock = lock 
     m_Lock.EnterReadLock() 
    End Sub 
    Public Sub Dispose() Implements IDisposable.Dispose 
     m_Lock.ExitReadLock() 
    End Sub 

End Class 
+1

Zwei Gedanken: Erstens sollten Sie m_Lock löschen, so dass eine doppelte Dispose() keine Probleme verursachen (unwahrscheinlich, aber ...) Sekunde - es gibt keine Notwendigkeit, für die Anrufer über ReadWrapper zu wissen, ob IDisposable würde genügen. Aber ich mag es ;-p –

+0

Guter Punkt, ich wollte den ReadWrapper-Typ sowieso nicht belichten. –

0

Da der Punkt eines Schlosses zu schützen Ich denke, dass es nützlich wäre, diesen Speicher in ein "Gesperrt" -Objekt einzubinden und es nur durch die verschiedenen Schlosstoken zugänglich zu machen (wie von Mark erwähnt):

// Stores a private List<T>, only accessible through lock tokens 
// returned by Read, Write, and UpgradableRead. 
var lockedList = new LockedList<T>(); 
using(var r = lockedList.Read()) { 
    foreach(T item in r.Reader) 
    ... 
} 
using(var w = lockedList.Write()) { 
    w.Writer.Add(new T()); 
} 
T t = ...; 
using(var u = lockedList.UpgradableRead()) { 
    if(!u.Reader.Contains(t)) 
    using(var w = u.Upgrade()) 
     w.Writer.Add(t); 
} 

Nun ist die einzige Möglichkeit, die interne Liste zuzugreifen, ist durch die entsprechende Zugriffs aufrufen.

Dies funktioniert besonders gut für List<T>, da es bereits die ReadOnlyCollection<T> Wrapper hat. Für andere Typen könnten Sie immer eine Locked<T,T> erstellen, aber dann verlieren Sie die Unterscheidung zwischen lesbar und schreibbar.

Eine Verbesserung könnte die R und W Typen als Einwegverpackungen definieren selbst, die gegen (versehentlichen) Fehler geschützt würde:

List<T> list; 
using(var w = lockedList.Write()) 
    list = w.Writable; 

//BAD: "locked" object leaked outside of lock scope 
list.MakeChangesWithoutHoldingLock(); 

Dies würde jedoch verwenden Locked komplizierter machen, und die Die aktuelle Version bietet den gleichen Schutz, den Sie beim manuellen Sperren eines freigegebenen Mitglieds haben.


sealed class LockedList<T> : Locked<List<T>, ReadOnlyCollection<T>> { 
    public LockedList() 
    : base(new List<T>(), list => list.AsReadOnly()) 
    { } 
} 

public class Locked<W, R> where W : class where R : class { 
    private readonly LockerState state_; 
    public Locked(W writer, R reader) { this.state_ = new LockerState(reader, writer); } 
    public Locked(W writer, Func<W, R> getReader) : this(writer, getReader(writer)) { } 

    public IReadable Read() { return new Readable(this.state_); } 
    public IWritable Write() { return new Writable(this.state_); } 
    public IUpgradable UpgradableRead() { return new Upgradable(this.state_); } 


    public interface IReadable : IDisposable { R Reader { get; } } 
    public interface IWritable : IDisposable { W Writer { get; } } 
    public interface IUpgradable : IReadable { IWritable Upgrade();} 


    #region Private Implementation Details 
    sealed class LockerState { 
    public readonly R Reader; 
    public readonly W Writer; 
    public readonly ReaderWriterLockSlim Sync; 

    public LockerState(R reader, W writer) { 
     Debug.Assert(reader != null && writer != null); 
     this.Reader = reader; 
     this.Writer = writer; 
     this.Sync = new ReaderWriterLockSlim(); 
    } 
    } 

    abstract class Accessor : IDisposable { 
    private LockerState state_; 
    protected LockerState State { get { return this.state_; } } 
    protected Accessor(LockerState state) { 
     Debug.Assert(state != null); 
     this.Acquire(state.Sync); 
     this.state_ = state; 
    } 

    protected abstract void Acquire(ReaderWriterLockSlim sync); 
    protected abstract void Release(ReaderWriterLockSlim sync); 

    public void Dispose() { 
     if(this.state_ != null) { 
     var sync = this.state_.Sync; 
     this.state_ = null; 
     this.Release(sync); 
     } 
    } 
    } 

    class Readable : Accessor, IReadable { 
    public Readable(LockerState state) : base(state) { } 
    public R Reader { get { return this.State.Reader; } } 
    protected override void Acquire(ReaderWriterLockSlim sync) { sync.EnterReadLock(); } 
    protected override void Release(ReaderWriterLockSlim sync) { sync.ExitReadLock(); } 
    } 

    sealed class Writable : Accessor, IWritable { 
    public Writable(LockerState state) : base(state) { } 
    public W Writer { get { return this.State.Writer; } } 
    protected override void Acquire(ReaderWriterLockSlim sync) { sync.EnterWriteLock(); } 
    protected override void Release(ReaderWriterLockSlim sync) { sync.ExitWriteLock(); } 
    } 

    sealed class Upgradable : Readable, IUpgradable { 
    public Upgradable(LockerState state) : base(state) { } 
    public IWritable Upgrade() { return new Writable(this.State); } 
    protected override void Acquire(ReaderWriterLockSlim sync) { sync.EnterUpgradeableReadLock(); } 
    protected override void Release(ReaderWriterLockSlim sync) { sync.ExitUpgradeableReadLock(); } 
    } 
    #endregion 
} 
2

Das ist nicht meine Erfindung, aber es hat sicherlich durch die Haare ein wenig weniger grau gemacht.

internal static class ReaderWriteLockExtensions 
{ 
    private struct Disposable : IDisposable 
    { 
     private readonly Action m_action; 
     private Sentinel m_sentinel; 

     public Disposable(Action action) 
     { 
      m_action = action; 
      m_sentinel = new Sentinel(); 
     } 

     public void Dispose() 
     { 
      m_action(); 
      GC.SuppressFinalize(m_sentinel); 
     } 
    } 

    private class Sentinel 
    { 
     ~Sentinel() 
     { 
      throw new InvalidOperationException("Lock not properly disposed."); 
     } 
    } 

    public static IDisposable AcquireReadLock(this ReaderWriterLockSlim lock) 
    { 
     lock.EnterReadLock(); 
     return new Disposable(lock.ExitReadLock); 
    } 

    public static IDisposable AcquireUpgradableReadLock(this ReaderWriterLockSlim lock) 
    { 
     lock.EnterUpgradeableReadLock(); 
     return new Disposable(lock.ExitUpgradeableReadLock); 
    } 

    public static IDisposable AcquireWriteLock(this ReaderWriterLockSlim lock) 
    { 
     lock.EnterWriteLock(); 
     return new Disposable(lock.ExitWriteLock); 
    } 
} 

Wie zu verwenden:

using (m_lock.AcquireReadLock()) 
{ 
    // Do stuff 
} 
3

die Lösungen bisher veröffentlicht Alle sind mit einem Risiko von Deadlock. A mit Block wie folgt aus:

ReaderWriterLockSlim sync = new ReaderWriterLockSlim(); 
using (sync.Read()) 
{ 
    // Do stuff 
} 

in etwa wie folgt umgewandelt wird:

ReaderWriterLockSlim sync = new ReaderWriterLockSlim(); 
IDisposable d = sync.Read(); 
try 
{ 
    // Do stuff 
} 
finally 
{ 
    d.Dispose(); 
} 

Das bedeutet, dass ein Threadabort (oder ähnliches) zwischen sync.Read() und try-Block passieren könnte. Wenn das passiert, wird der finally Block nie aufgerufen und die Sperre wird nie freigegeben!

Weitere Informationen und eine bessere Umsetzung zu sehen: Deadlock with ReaderWriterLockSlim and other lock objects

Auch aus Joe Duffy's Blog

Als nächstes wird die Sperre auf asynchrone Ausnahmen nicht robust ist wie Thread bricht und aus dem Speicher Bedingungen. Wenn einer dieser Fälle auftritt, während sich eine der Methoden der Sperre befindet, kann der Sperrstatus beschädigt werden, was zu nachfolgenden Deadlocks, unbehandelten Ausnahmen und (leider) aufgrund der internen Verwendung von Spin Locks zu einer fixierten 100% CPU führt. Wenn Sie also Ihren Code in einer Umgebung ausführen, in der regelmäßig Thread-Abbrüche ausgeführt werden oder versucht wird, harte OOMs zu überstehen, werden Sie mit dieser Sperre nicht zufrieden sein.

+0

Wenn jemand ThreadAbortExceptions wirft, dann gibt es weit ernstere Probleme als nur einen Deadlock. Dann ist NUR eine Zeit, die eine ThreadAbortException geeignet ist, wenn sie vom Thread selbst ausgelöst wird, zum Beispiel beim Aufruf von HttpResponse.End. –

+0

Ich denke, das ist ein großartiger Punkt und sollte mehr Aufmerksamkeit erhalten. Marc Gravells Antwort hat mich wirklich angezogen, bis ich das gelesen habe. – Pandincus

+1

... beide Links sind tot. – Beachwalker