2008-11-10 23 views
58

Tengo un montón de propiedades en las que voy a usar bloqueos de lectura/escritura. Puedo implementarlos con una cláusula try finally o using.'using' declaración vs 'try finally'

En el try finally adquiriría el candado antes del try, y lo liberaré en el finally. En la cláusula using, crearía una clase que adquiera el bloqueo en su constructor y las libere en su método Dispose.

Estoy usando bloqueos de lectura/escritura en muchos lugares, así que he estado buscando formas que podrían ser más concisas que try finally. Estoy interesado en escuchar algunas ideas sobre por qué no se puede recomendar una manera, o por qué una podría 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(); 
    } 
} 

Respuesta

73

De MSDN, using Statement (C# Reference)

La instrucción using asegura que Desechar se llama incluso si se produce una excepción, mientras que la que está llamando a métodos en el objeto. Puede lograr el mismo resultado colocando el objeto dentro de un bloque try y luego llamando a Dispose en un bloque finally; de hecho, así es como el compilador traduce la sentencia using. El ejemplo de código se expande anterior al siguiente código en tiempo de compilación (tenga en cuenta los apoyos adicionales rizado para crear el alcance limitado para el objeto):

{ 
    Font font1 = new Font("Arial", 10.0f); 
    try 
    { 
    byte charset = font1.GdiCharSet; 
    } 
    finally 
    { 
    if (font1 != null) 
     ((IDisposable)font1).Dispose(); 
    } 
} 

Así que, básicamente, es el mismo código pero con un buen null-checks automáticos 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.

Vaya con la opción 2.

Tener la variable dentro de un alcance que termina inmediatamente después de que ya no se necesita también es un plus.

+0

qué es lo mejor para liberar un recurso que no se puede instanciar dentro de la instrucción de uso o que se reutiliza o se pasa como out param? Try/catch !!! – bjan

+2

@bjan bien, ¿por qué estás considerando 'usar' en ese caso? eso no es para lo que 'usar '. – chakrit

+1

es por eso que también menciono 'try/catch' ya que parece la única forma de manejarlo a través del bloque' try/catch/finally'. hope 'using' podría manejar esto también – bjan

11

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 edita el código debe tener cuidado de no insertar nada entre la llamada de bloqueo Adquirir (Leer | Escribir) y la intentona.

(El uso de un bloqueo de lectura/escritura en propiedades individuales como este suele ser exagerado. Se aplican mejor a un nivel mucho más alto. A menudo bastará con un simple bloqueo ya que la posibilidad de contención es presumiblemente muy pequeña dado el tiempo el bloqueo se mantiene, y la adquisición de un bloqueo de lectura/escritura es una operación más costosa que un simple bloqueo).

+0

¿Qué hay de prueba de fallos? Sé que finalmente intentarán disparar el bloque final, ¿hay alguna forma de que no se llame al disposer? – Jeremy

+1

No, la declaración using es esencialmente azúcar sintáctica para el patrón try/finally. –

+0

El modelo que utiliza asegura que el objeto siempre será eliminado. –

0

Creo que el método 2 sería mejor.

  • Código más simple y más legible en sus propiedades.
  • Menos propenso a errores, ya que el código de bloqueo no tiene que volver a escribirse varias veces.
3

DRY dice: second solution. La primera solución duplica la lógica de usar un bloqueo, mientras que la segunda no.

1

Los bloques de prueba/captura generalmente son para el manejo de excepciones, mientras que el uso de bloques se utiliza para asegurar que el objeto se elimine.

Para la lectura/escritura de un cierre de try/catch podría ser el más útil, pero también se puede utilizar tanto, así:

using (obj) 
{ 
    try { } 
    catch { } 
} 

para que implícitamente puede llamar a su interfaz IDisposable, así como hacer manejo de excepciones conciso.

4

me gusta la tercera opción

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 el más limpio y más fácil de entender lo que está pasando.

+0

instrucción de bloqueo no funciona de la misma forma que ReaderWriterLock. – chakrit

+0

@chakrit: No, pero a menos que sepa que realmente existe una disputa de bloqueo, es probable que sea más eficiente. –

+0

Es cierto, pero siempre debe elegir primero el bloqueo smiple. La pregunta del subgrupo no dice nada sobre los problemas de rendimiento o los requisitos que no permitirán un bloqueo. Por lo tanto, no hay razón para intentar ser astuto. Solo trábelo y cuélgalo. – Will

8

Considere la posibilidad de que ambas soluciones sean malas porque enmascaran las excepciones.

A try sin un catch obviamente debería ser una mala idea; ver MSDN para saber por qué la declaración using es igualmente peligrosa.

Nota: Microsoft también recomienda ReaderWriterLockSlim en lugar de ReaderWriterLock.

Finalmente, tenga en cuenta que los ejemplos de Microsoft usan dos bloques try-catch para evitar estos problemas, p.

try 
{ 
    try 
    { 
     //Reader-writer lock stuff 
    } 
    finally 
    { 
     //Release lock 
    } 
} 
catch(Exception ex) 
{ 
    //Do something with exception 
} 

Una solución simple y consistente, limpio es un buen objetivo, pero suponiendo que no sólo puede utilizar lock(this){return mydateetc;}, usted podría reconsiderar el enfoque; con más información estoy seguro Stack   Overflow puede ayudar ;-)

+1

Una prueba finalmente no oculta necesariamente la excepción. En mi ejemplo, se adquiere la cerradura, luego si se arroja alguna excepción dentro del alcance, se liberará la cerradura, pero la excepción seguirá burbujeando. – Jeremy

+1

@Jeremy: si su bloque finally arroja una excepción, enmascara la excepción lanzada en su bloque try - eso es lo que el artículo msdn decía que era el (mismo) problema con la sintaxis que usa –

+3

No enmascara la excepción, es ' Reemplazaremos la excepción exactamente de la misma manera que la tuya. –

5

Yo personalmente uso la declaración C# "using" tan a menudo como sea posible, pero hay algunas cosas específicas que hago junto con esto para evitar el potencial problemas 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 el "uso" declaración en un método y el uso del objeto (s) en otro método con un bloque de "probar"/"captura". Puedo anidar varias declaraciones de "uso" como esta para objetos relacionados (a veces tengo tres o cuatro letras de profundidad en mi código de producción).

En mis métodos Dispose() para estas clases personalizadas IDisposable, capturo excepciones (pero NO errores) y las registro (usando Log4net). Nunca me encontré con una situación en la que alguna de esas excepciones pudiera afectar mi procesamiento. Los errores potenciales, como de costumbre, pueden propagarse por la pila de llamadas y normalmente terminan el procesamiento con un mensaje apropiado (el error y el seguimiento de la pila) registrado.

Si de alguna manera encontré una situación en la que podría ocurrir una excepción significativa durante Dispose(), 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 características favoritas de C#.Por cierto, trabajo en Java, C# y Python como mis lenguajes principales, con muchos otros arrojados aquí y allá, y "usar" es una de mis características de lenguaje favoritas porque es un caballo de batalla práctico y cotidiano. .

+0

Sugerencia, para la legibilidad del código: alinee y comprima las declaraciones de usos al no tener llaves, excepto para el "uso" interno. –

4

"Montón de propiedades" y el bloqueo en el nivel de captador de propiedades y del sistema se ve mal. Su bloqueo es demasiado fino. En el uso de objetos más típico, le recomendamos asegurarse de haber adquirido un bloqueo para acceder al más que una propiedad al mismo tiempo. Su caso específico podría ser diferente, pero lo dudo.

De todos modos, adquirir el candado cuando acceda al objeto en lugar de la propiedad reducirá significativamente la cantidad de código de bloqueo que tendrá que escribir.

+0

Sí, definitivamente veo tu punto. La mayoría de mis propiedades son en realidad bools, ints, que no necesitaría bloquear porque deberían ser atómicas. Hay algunos de fecha y hora, de cadena, que me gustaría que cerraran. porque la minoría necesitaría cerraduras, lo mejor es ponerlo en la propiedad – Jeremy

0

Si bien estoy de acuerdo con muchos de los comentarios anteriores, incluida la granularidad del bloqueo y el manejo cuestionable de excepciones, la pregunta es de enfoque. Déjame darte una razón importante por la que prefiero utilizar el truco {} finalmente modelo ... abstracción.

Tengo un modelo muy similar al suyo 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 del tipo ILock, se puede usar para establecer un ámbito 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. La implementación de su bloqueo puede ser un bloqueo de monitor, un bloqueo de lector, un bloqueo de escritor o un 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 la cerradura lo haga de manera consistente con su implementación.

interface ILock { 
    IDisposable Acquire(); 
} 

class MonitorLock : ILock { 
    IDisposable Acquire() { ... acquire the lock for real ... } 
} 

Me gusta su modelo, pero consideraría ocultar la mecánica de bloqueo de la persona que llama. FWIW, he medido la sobrecarga de la técnica de uso versus la prueba final y la sobrecarga de asignar el objeto desechable tendrá entre un 2-3% de rendimiento por encima.

0

Me sorprende que nadie haya sugerido encapsular el try-finally en funciones anónimas. Al igual que la técnica de creación de instancias y eliminación de clases con la instrucción using, esto mantiene el bloqueo en un solo lugar. Prefiero esto solo porque prefiero leer la palabra "finalmente" que la palabra "Eliminar" cuando estoy pensando en liberar 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(); 
     } 
    } 
} 
-1

Silly me. Hay una manera de hacerlo aún más simple al hacer que los métodos bloqueados sean parte de cada instancia (en lugar de estar estáticos como en mi publicación anterior). Ahora realmente lo prefiero porque no hay necesidad de pasar `rwlMyLock_m 'a alguna 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(); 
     } 
    } 
} 
+1

Simplemente edite su otra respuesta, no la duplique. – TheSoftwareJedi

0

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 fija 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(); 
     } 
    } 
} 
0

En realidad, en su primera ejemplo, para hacer las soluciones comparables, también se llevaría a cabo "IDisposable" también allí, y llamar "Eliminar" en el bloque "último" en lugar de liberar el bloqueo directamente . Entonces sería la implementación de "manzanas a manzanas" (y MSIL) -wise (MSIL será la misma para ambas soluciones).Todavía es una buena idea usar "usar" debido a la ampliación del alcance y porque el Framework asegurará el uso correcto de "IDisposable" (este último es menos beneficioso si está implementando "IDisposabe").

0

El 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(); 
    } 
} 
Cuestiones relacionadas