2008-11-02 12 views
5

Un patrón común en C++ es crear una clase que envuelve un bloqueo: el bloqueo se toma implícitamente cuando se crea un objeto o se toma explícitamente después. Cuando el objeto sale del alcance, dtor libera automáticamente el bloqueo. ¿Es posible hacer esto en C#? Por lo que entiendo, no hay garantías cuando dtor en C# se ejecutará después de que el objeto se salga del alcance.¿Es posible implementar el bloqueo de ámbito en C#?

Aclaración: Cualquier bloqueo en general, spinlock, ReaderWriterLock, lo que sea. Llamando Eliminarme derrota el propósito del patrón - liberar el bloqueo tan pronto como salgamos del alcance - sin importar si llamamos a la devolución en el medio, lanzamos una excepción o lo que sea. Además, por lo que entiendo usar todavía solo cola objeto para GC, no destruirlo de inmediato ...

Respuesta

5

Su comprensión con respecto a using es incorrecta, esta es una forma de que las acciones del alcance sucedan de forma determinista (no se produce ninguna cola al GC).

C# suministra la palabra clave lock que proporciona un bloqueo exclusivo y si usted quiere tener diferentes tipos (por ejemplo, lectura/escritura) que tendrá que utilizar la instrucción using.

P.S. This thread puede interesarte.

4

Es cierto que no sabe exactamente cuándo se ejecutará el dtor ... pero, si implementas la interfaz IDisposable, y luego utilizas un bloque 'using' o llamas 'Dispose()', tendrás un lugar donde ubicar tu código.

Pregunta: Cuando dices "bloquear", ¿te refieres a un bloqueo de hilo para que solo un hilo a la vez pueda usar el objeto? Como en:

lock (_myLockKey) { ... } 

Por favor, aclare.

12

Para amplificar la respuesta de Timothy, la instrucción de bloqueo crea un bloqueo de ámbito utilizando un monitor. Esencialmente, esto se traduce en algo como esto:

lock(_lockKey) 
{ 
    // Code under lock 
} 

// is equivalent to this 
Monitor.Enter(_lockKey) 
try 
{ 
    // Code under lock 
} 
finally 
{ 
    Monitor.Exit(_lockKey) 
} 

En C# que rara vez se utiliza el dtor para este tipo de patrón (véase la instrucción using/IDisposable). Una cosa que puede notar en el código es que si ocurre una excepción asíncrona entre Monitor.Enter y la prueba, parece que el monitor no se lanzará. El JIT en realidad ofrece una garantía especial de que si un Monitor.Enter precede inmediatamente a un bloque de prueba, la excepción asíncrona no ocurrirá hasta que el bloque try asegure la liberación.

3

Para completar, hay otra forma de lograr un efecto RAII similar sin usar using y IDisposable. En C# using suele ser más claro (see also here para algunos pensamientos más), pero en otros idiomas (por ejemplo, Java), o incluso en C# si using no es apropiado por alguna razón, es útil saberlo.

Se trata de un lenguaje llamado "Ejecutar alrededor" y la idea es que se llama a un método que hace las cosas pre y post (por ejemplo, bloqueo/desbloqueo de los hilos, o la creación y cometiendo/cierre de la conexión de base de datos, etc.), y usted pasa a ese método un delegado que implementará las operaciones que desea que ocurran en el medio.

p. Ej.:


funkyObj.InOut(delegate{ System.Console.WriteLine("middle bit"); }); 

Dependiendo de lo que hace el método InOut, la salida podría ser algo como:

 
first bit 
middle bit 
last bit 

Como digo, esta respuesta es por la totalidad única, las sugerencias anteriores de using con IDisposable, como así como la palabra clave lock, va a ser mejor el 99% del tiempo.

Es una lástima que, si bien .Net ha ido más allá de muchos otros lenguajes de OO modernos en este aspecto (te estoy mirando, Java), todavía se responsabiliza de RAII para trabajar en el código de cliente (es decir, el código que usa using), mientras que en C++ el destructor siempre se ejecutará al final del alcance.

0

Me ha molestado mucho el hecho de que using es responsabilidad del desarrollador recordar que hacer; en el mejor de los casos, recibe una advertencia, que la mayoría de las personas nunca se molesta en promover a un error. Entonces, he estado jugando con una idea como esta: obliga al cliente a al menos INTENTAR hacer las cosas correctamente. Afortunada y desafortunadamente, es un cierre, por lo que el cliente podría conservar una copia del recurso e intentar usarlo más tarde, pero este código al menos intenta empujar al cliente en la dirección correcta ...

public class MyLockedResource : IDisposable 
{ 
    private MyLockedResource() 
    { 
     Console.WriteLine("initialize"); 
    } 

    public void Dispose() 
    { 
     Console.WriteLine("dispose"); 
    } 

    public delegate void RAII(MyLockedResource resource); 

    static public void Use(RAII raii) 
    { 
     using (MyLockedResource resource = new MyLockedResource()) 
     { 
      raii(resource); 
     } 
    } 

    public void test() 
    { 
     Console.WriteLine("test"); 
    } 
} 

buen uso:

MyLockedResource.Use(delegate(MyLockedResource resource) 
{ 
    resource.test(); 
}); 

uso de malo! (Desafortunadamente, esto no se puede evitar ...)

MyLockedResource res = null; 
MyLockedResource.Use(delegate(MyLockedResource resource) 
{ 
    resource.test(); 
    res = resource; 
    res.test(); 
}); 
res.test(); 
Cuestiones relacionadas