Tengo un montón de propiedades en las que voy a usar bloqueos de lectura / escritura. Puedo implementarlos con una try finally
o una using
cláusula.
En el try finally
I adquiriría el bloqueo antes del try
, y lo liberaría en el finally
. En la using
cláusula, crearía una clase que adquiere el bloqueo en su constructor y lo libera en su método Dispose.
Estoy usando bloqueos de lectura / escritura en muchos lugares, así que he estado buscando formas que puedan ser más concisas que try finally
. Estoy interesado en escuchar algunas ideas sobre por qué una forma puede no ser recomendada o por qué una puede ser mejor que otra.
Método 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();
}
}
}
Método 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
fuente
fuente
Respuestas:
Desde MSDN, usando Statement (Referencia de C #)
{ Font font1 = new Font("Arial", 10.0f); try { byte charset = font1.GdiCharSet; } finally { if (font1 != null) ((IDisposable)font1).Dispose(); } }
Básicamente, es el mismo código pero con una buena verificación automática de nulos y un alcance adicional para su variable . La documentación también establece que "asegura el uso correcto del objeto IDisposable", por lo que también podría obtener un soporte de marco aún mejor para cualquier caso oscuro en el futuro.
Así que ve con la opción 2.
Tener la variable dentro de un alcance que finaliza inmediatamente después de que ya no se necesita también es una ventaja.
fuente
using
en ese caso? eso nousing
es para lo que es.try/catch
que parece la única forma de manejar a través deltry/catch/finally
bloque. esperabausing
poder manejar esto tambiéntry/finally
supongo que es tu única opción en ese caso. En mi opinión, sin embargo, creo que siempre debería haber algún objeto / pieza de código responsable de mantener la vida útil del objeto en tales casos (donde siempre se debería llamar a Dispose ()). Si una clase solo maneja la instanciación y alguien más tiene que recordar para tirarlo creo que huele un poco allí. No estoy seguro de cómo agregaría eso a nivel de idioma.Definitivamente prefiero el segundo método. Es más conciso en el punto de uso y menos propenso a errores.
En el primer caso, alguien que edite el código debe tener cuidado de no insertar nada entre la llamada de bloqueo de adquisición (lectura | escritura) y el intento.
(Sin embargo, usar un bloqueo de lectura / escritura en propiedades individuales como esta suele ser excesivo. Es mejor aplicarlos a un nivel mucho más alto. Un bloqueo simple a menudo será suficiente aquí, ya que la posibilidad de disputa es presumiblemente muy pequeña dado el tiempo que se mantiene el bloqueo. para, y adquirir un bloqueo de lectura / escritura es una operación más costosa que un simple bloqueo).
fuente
Considere la posibilidad de que ambas soluciones sean malas porque enmascaran excepciones .
Un
try
sin uncatch
obviamente debería ser una mala idea; consulte MSDN para saber por qué lausing
declaración es igualmente peligrosa.Tenga en cuenta también que Microsoft ahora recomienda ReaderWriterLockSlim en lugar de ReaderWriterLock.
Por último, tenga en cuenta que los ejemplos de Microsoft utilizan dos bloques try-catch para evitar estos problemas, p. Ej.
try { try { //Reader-writer lock stuff } finally { //Release lock } } catch(Exception ex) { //Do something with exception }
Una solución simple, consistente y limpia es un buen objetivo, pero asumiendo que no puede simplemente usar
lock(this){return mydateetc;}
, podría reconsiderar el enfoque; con más información, estoy seguro de que Stack Overflow puede ayudar ;-)fuente
Yo personalmente uso la instrucción "using" de C # con tanta frecuencia como sea posible, pero hay algunas cosas específicas que hago junto con ella para evitar los problemas potenciales mencionados. Para ilustrar:
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 } }
Tenga en cuenta que separo la instrucción "using" en un método y el uso del objeto (s) en otro método con un bloque "try" / "catch". Puedo anidar varias declaraciones de "uso" como esta para objetos relacionados (a veces profundizo tres o cuatro en mi código de producción).
En mis
Dispose()
métodos para estasIDisposable
clases personalizadas , capturo excepciones (pero NO errores) y las registro (usando Log4net). Nunca me he encontrado con una situación en la que alguna de esas excepciones pueda afectar mi procesamiento. Se permite que los errores potenciales, como de costumbre, se propaguen por la pila de llamadas y, por lo general, finalicen el procesamiento con un mensaje apropiado (el error y el seguimiento de la pila) registrado.Si de alguna manera me encontrara con una situación en la que pudiera ocurrir una excepción significativa durante
Dispose()
, lo rediseñaría para esa situación. Francamente, dudo que eso suceda alguna vez.Mientras tanto, el alcance y las ventajas de limpieza de "usar" lo convierten en una de mis funciones favoritas de C #. Por cierto, trabajo en Java, C # y Python como mis lenguajes principales, con muchos otros lanzados aquí y allá, y "usar" es una de mis características de lenguaje favoritas porque es un caballo de batalla práctico y diario. .
fuente
Me gusta la tercera opcion
private object _myDateTimeLock = new object(); private DateTime _myDateTime; public DateTime MyDateTime{ get{ lock(_myDateTimeLock){return _myDateTime;} } set{ lock(_myDateTimeLock){_myDateTime = value;} } }
De sus dos opciones, la segunda opción es la más limpia y fácil de entender lo que está sucediendo.
fuente
"Montón de propiedades" y el bloqueo en el nivel de captador y definidor de propiedades parece incorrecto. Su bloqueo es demasiado fino. En el uso de objetos más típico, querrá asegurarse de haber adquirido un candado para acceder a más de una propiedad al mismo tiempo. Su caso específico puede ser diferente, pero lo dudo un poco.
De todos modos, adquirir el candado cuando acceda al objeto en lugar de a la propiedad reducirá significativamente la cantidad de código de bloqueo que tendrá que escribir.
fuente
SECO dice: segunda solución. La primera solución duplica la lógica del uso de un candado, mientras que la segunda no.
fuente
Los bloques Try / Catch son generalmente para el manejo de excepciones, mientras que los bloques de uso se utilizan para garantizar que el objeto se elimine.
Para el bloqueo de lectura / escritura, un try / catch puede ser lo más útil, pero también puede usar ambos, así:
using (obj) { try { } catch { } }
para que pueda llamar implícitamente a su interfaz IDisposable, así como hacer conciso el manejo de excepciones.
fuente
Creo que el método 2 sería mejor.
fuente
Si bien estoy de acuerdo con muchos de los comentarios anteriores, incluida la granularidad del bloqueo y el manejo de excepciones cuestionables, la cuestión es de enfoque. Permítanme darles una gran razón por la que prefiero usar el modelo try {} finalmente ... abstracción.
Tengo un modelo muy similar al tuyo con una excepción. Definí una interfaz base ILock y en ella proporcioné un método llamado Acquire (). El método Acquire () devolvió el objeto IDisposable y, como resultado, significa que siempre que el objeto con el que estoy tratando sea de tipo ILock, se puede usar para hacer un alcance de bloqueo. ¿Porque es esto importante?
Nos ocupamos de muchos mecanismos y comportamientos de bloqueo diferentes. Su objeto de bloqueo puede tener un tiempo de espera específico que emplea. Su implementación de bloqueo puede ser un bloqueo de monitor, bloqueo de lector, bloqueo de escritura o bloqueo de giro. Sin embargo, desde la perspectiva de la persona que llama, todo eso es irrelevante, lo que les importa es que el contrato para bloquear el recurso se cumpla y que el bloqueo lo haga de manera coherente con su implementación.
interface ILock { IDisposable Acquire(); } class MonitorLock : ILock { IDisposable Acquire() { ... acquire the lock for real ... } }
Me gusta tu modelo, pero consideraría ocultar la mecánica de la cerradura a la persona que llama. FWIW, he medido la sobrecarga de la técnica de uso frente a la de intentar finalmente y la sobrecarga de asignar el objeto desechable tendrá entre un 2-3% de sobrecarga de rendimiento.
fuente
Me sorprende que nadie haya sugerido encapsular el intento finalmente en funciones anónimas. Al igual que la técnica de instanciar y eliminar clases con la instrucción using, esto mantiene el bloqueo en un solo lugar. Yo prefiero esto solo porque prefiero leer la palabra "finalmente" que la palabra "Eliminar" cuando estoy pensando en abrir un candado.
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(); } } }
fuente
SoftwareJedi, no tengo una cuenta, así que no puedo editar mis respuestas.
En cualquier caso, la versión anterior no era realmente buena para uso general, ya que el bloqueo de lectura siempre requería un valor de retorno. Esto corrige que:
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(); } } }
fuente
Lo siguiente crea métodos de extensión para la clase ReaderWriterLockSlim que le permiten hacer lo siguiente:
var rwlock = new ReaderWriterLockSlim(); using (var l = rwlock.ReadLock()) { // read data } using (var l = rwlock.WriteLock()) { // write data }
Aquí está el código:
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(); } }
fuente
En realidad, en su primer ejemplo, para que las soluciones sean comparables, también las implementaría
IDisposable
allí. Entonces llamaríasDispose()
desde elfinally
bloque en lugar de liberar el bloqueo directamente.Entonces sería una implementación de "manzanas con manzanas" (y MSIL) en sentido (MSIL será el mismo para ambas soluciones). Probablemente sea una buena idea usar
using
debido al alcance agregado y porque el Framework garantizará el uso adecuado deIDisposable
(este último es menos beneficioso si lo está implementandoIDisposable
usted mismo).fuente
Tonto de mí. Hay una manera de hacerlo aún más simple haciendo que los métodos bloqueados formen parte de cada instancia (en lugar de estáticos como en mi publicación anterior). Ahora realmente prefiero esto porque no hay necesidad de pasar `rwlMyLock_m 'a otra clase o método.
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(); } } }
fuente