2008-10-04 10 views

Respuesta

16

Estaba pensando lo mismo, pero en 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 gran fragmento! –

+1

Es curioso, hace tiempo que me había olvidado de esto y, por pura casualidad, aparece en mi reputación de la misma manera que podría beneficiarme de ello. –

0

Terminé haciendo esto, pero todavía estoy abierto a mejores formas o fallas en mi diseño.

Using m_Lock.ReadSection 
    Return m_List.Count 
End Using 

Este utiliza este método de extensión/clase:

<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

Dos pensamientos: en primer lugar, usted debe limpiar m_Lock de manera que un doble Dispose() no causa problemas (poco probable, pero ...) segundos - no hay necesidad de que la persona que llama para saber acerca de si ReadWrapper IDisposable bastaría. Pero me gusta ;-p –

+0

Buen punto, no quería exponer el tipo ReadWrapper de todos modos. –

0

Desde el punto de una cerradura es proteger un poco de memoria, creo que sería útil envolver esa memoria en un objeto "Bloqueado", y solo hacerla accesible a través de varios tokens de bloqueo (como se menciona en Mark):

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

Ahora la única manera de acceder a la lista interna es llamando el descriptor de acceso apropiado.

Esto funciona especialmente bien para List<T>, ya que ya tiene la envoltura ReadOnlyCollection<T>. Para otros tipos, siempre puede crear un Locked<T,T>, pero luego pierde la buena distinción de tipo legible/grabable.

Una mejora podría ser la definición de los R y W tipos como ellos mismos envoltorios desechables, lo cual protegidos contra errores (inadvertidos) como:

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

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

Sin embargo, esto haría que Locked más complicado de usar, y la la versión actual sí le brinda la misma protección que tiene cuando bloquea manualmente un miembro compartido.


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

Ésta no es mi invención, pero que sin duda ha hecho por el pelo un poco menos gris.

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

Modo de empleo:

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

Todas las soluciones publicadas hasta ahora están en riesgo de estancamiento.Un bloque de usando la siguiente manera:

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

se convierte en algo parecido a esto:

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

Esto significa que un ThreadAbortException (o similar) podría ocurrir entre sync.Read() y el bloque try. Cuando esto sucede, nunca se llama al bloque finally y nunca se libera el bloqueo.

Para obtener más información y una mejor aplicación ver: Deadlock with ReaderWriterLockSlim and other lock objects

Asimismo, desde Joe Duffy's Blog

A continuación, el bloqueo no es robusto a excepciones asíncronas como hilo aborta y fuera de las condiciones de memoria. Si uno de estos ocurre mientras está en medio de uno de los métodos de bloqueo, el estado de bloqueo puede estar dañado, lo que puede causar bloqueos, excepciones no controladas y (lamentablemente) debido al uso de bloqueos de giro internos, una CPU 100% fija. Por lo tanto, si va a ejecutar su código en un entorno que utiliza interrupciones de subprocesos o intentos de sobrevivir OOM difíciles, no estará contento con este bloqueo.

+0

Si alguien está lanzando alrededor de ThreadAbortExceptions, entonces hay problemas mucho más serios que simplemente un punto muerto. Entonces, SOLO el tiempo en que una ThreadAbortException es apropiada es cuando la provoca el propio hilo, como cuando se llama a HttpResponse.End. –

+0

Creo que este es un buen punto y debería recibir más atención. Me atrajo realmente la respuesta de Marc Gravell hasta que leí esto. – Pandincus

+1

... ambos enlaces están muertos. – Beachwalker

Cuestiones relacionadas