c# .net vb.net parallel-processing readerwriterlock

c# - ¿Cómo simplificaría Entrar y salir de ReaderWriterLock?



.net vb.net (5)

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

Esto me parece muy ruidoso. Cinco líneas de sobrecarga son demasiado.

m_Lock.EnterReadLock() Try Return m_List.Count Finally m_Lock.ExitReadLock() End Try

Entonces, ¿cómo es que simplemente esto?


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

Esto usa este método / clase de extensión:

<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


Este no es mi invento, pero ciertamente ha sido hecho por el cabello 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); } }

Cómo utilizar:

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


Todas las soluciones publicadas hasta ahora están en riesgo de estancamiento. Un bloque que usa así:

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

se convierte en algo como esto:

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

Esto significa que podría ocurrir una ThreadAbortException (o similar) 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 implementación, vea: Deadlock con ReaderWriterLockSlim y otros objetos de bloqueo

Además, del blog de Joe Duffy

A continuación, el bloqueo no es robusto para las excepciones asíncronas, como abortos de subprocesos y condiciones de falta 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.


Como el objetivo de un bloqueo es proteger algún trozo de memoria, creo que sería útil envolver esa memoria en un objeto "Bloqueado", y solo hacer que sea accesible mediante los diversos tokens de bloqueo (como menciona 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 forma de acceder a la lista interna es llamando al acceso apropiado.

Esto funciona particularmente bien para List<T> , ya que ya tiene el contenedor ReadOnlyCollection<T> . Para otros tipos, siempre puedes crear un Locked<T,T> , pero luego pierdes la agradable distinción de tipo legible / grabable.

Una mejora podría ser definir los tipos R y W como envoltorios desechables, que protegerían contra errores (involuntarios) 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 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 }