.net - example - synlock c#
Comprobando si el hilo actual posee un bloqueo (11)
Supongamos que tengo el siguiente código:
public class SomeClass()
{
private readonly object _lock = new object();
public void SomeMethodA()
{
lock (_lock)
{
SomeHelperMethod();
//do something that requires lock on _lock
}
}
public void SomeMethodB()
{
lock (_lock)
{
SomeHelperMethod();
//do something that requires lock on _lock
}
}
private void SomeHelperMethod()
{
lock (_lock)
{
//do something that requires lock on _lock
}
}
}
Bloquear dentro de SomeHelperMethod
parece redundante y derrochador, ya que todas las personas que llaman ya han tomado un bloqueo. Sin embargo, simplemente eliminar el bloqueo de SomeHelperMethod
parece peligroso ya que más adelante podríamos refactorizar el código y olvidarnos de bloquear el objeto _lock
antes de llamar SomeHelperMethod
.
Idealmente, podría evitar esto afirmando que el hilo actual posee un bloqueo en _lock
dentro de SomeHelperMethod
:
private void SomeHelperMethod()
{
Debug.Assert(Monitor.HasLock(_lock));
//do something that requires lock on _lock
}
Pero no parece haber tal método. Monitor.TryEnter
no ayuda porque los bloqueos son reentrantes. Por lo tanto, si el hilo actual ya posee el bloqueo, TryEnter
seguirá teniendo éxito y se devolverá true
. La única vez que fallará es si otro hilo posee el bloqueo y la llamada expira.
Entonces, ¿existe tal método? Si no, ¿por qué? No me parece nada peligroso, ya que simplemente te dice si el hilo actual (no otro hilo) posee un bloqueo o no.
Tenía requisitos similares de verificar si un bloqueo está bloqueado por el hilo actual, entonces puedo hacer algo como esto.
public void Method1()
{
if(!lockHeld()) lock();
//DO something
}
public void Method2()
{
if(!lockHeld()) lock();
//DO something
}
public void Method3()
{
if(!lockHeld()) lock();
//Do something
unlock()
}
Entonces me escribí una clase para eso:
public class LockObject
{
private Object internalLock;
private bool isLocked;
public bool IsLocked
{
get { lock (internalLock) return isLocked; }
private set { lock (internalLock) isLocked = value; }
}
public LockObject()
{
internalLock = new object();
}
public void UsingLock(Action method)
{
try
{
Monitor.Enter(this);
this.IsLocked = true;
method();
}
finally
{
this.IsLocked = false;
Monitor.Exit(this);
}
}
}
Entonces puedo usarlo como:
public void Example()
{
var obj = new LockObject();
obj.UsingLock(() =>
{
//Locked, and obj.IsLocked = true
});
}
Nota: Omití los métodos Lock () Unlock () en esta clase, pero prácticamente solo envoltorios para Monitor.Enter / Exit que establece IsLocked en verdadero. El ejemplo simplemente ilustra que es muy similar a lock () {} en términos de estilo.
Puede ser innecesario, pero esto es útil para mí.
No es lo mejor que puedes hacer, pero ...
bool OwnsLock(object someLockObj)
{
if (Monitor.TryEnter(someLockObj))
{
Monitor.Exit();
return false;
}
return true;
}
Debug.Assert(OwnsLock(_someLockObject), "_someLockObject should be locked when this method is called")
OK, ahora entiendo tu problema. Conceptualmente, ahora estoy pensando en un objeto Semaphore. Puede verificar fácilmente el valor del semáforo (un valor mayor o igual que 0 significa que el recurso está disponible). Además, esto no bloquea realmente el recurso que se realiza al incrementar o disminuir el valor del semáforo. El objeto Semaphore está presente en .NET framework pero no lo usé, así que no puedo proporcionar un ejemplo adecuado. Si es necesario, puedo construir un ejemplo simple y publicarlo aquí. Házmelo saber.
Alex.
LE No estoy seguro ahora si realmente tiene control de los mecanismos de sincronización en la aplicación. Si solo tiene acceso a un objeto que podría estar bloqueado o no, mi solución podría probar (una vez más) que no sirve.
Ojalá fuera posible, eso haría que mi código fuera mucho, mucho más limpio.
Bloquear o volver a bloquear son efectivamente gratis. La desventaja de este bajo costo es que no tiene tantas características como los otros mecanismos de sincronización. Simplemente bloquearía cada vez que un bloqueo sea vital, como lo has hecho anteriormente.
Si desea por omisión omitir bloqueos "innecesarios" sin el riesgo que conlleva la potencial refactorización, agregue comentarios. Si alguien cambia tu código y se rompe, hay un comentario allí que explica por qué. Si todavía no pueden resolverlo, ese es su problema, no el tuyo. Otra alternativa es crear una clase para usar como un objeto de bloqueo que contiene un booleano que puede activarse y desactivarse. Sin embargo, los gastos generales introducidos al hacer esto (incluidos los bloques try / finally, que no son gratuitos), probablemente no valen la pena.
No tengo experiencia alguna con la programación de .NET, pero en mi propio código (Delphi) una vez implementé dichas afirmaciones al tener métodos Adquirir () y Release () personalizados en una clase de sección crítica, con una variable de miembro privado para la identificación de el hilo ha adquirido el cs. Después de EnterCriticalSection () se escribió el valor de retorno de GetCurrentThreadID, y antes de LeaveCriticalSection () se restableció el valor del campo.
Pero no sé si .NET permite algo similar, o si podría implementar esto en su programa ...
Pero no parece haber tal método. Monitor.TryEnter no ayuda porque los bloqueos son reentrantes. Por lo tanto, si el hilo actual ya posee el bloqueo, TryEnter seguirá teniendo éxito y se devolverá verdadero. La única vez que fallará es si otro hilo posee el bloqueo y la llamada expira.
Encontré una solución que funciona de manera similar a Monitor.TryEnter () y aún no tiene los problemas de reingreso. Básicamente, en lugar de usar Monitor.TryEnter (), puede usar Monitor.Pulse (). Esta función arroja una excepción SynchronizationLockException si el hilo actual no mantiene el bloqueo.
ejemplo:
try
{
System.Threading.Monitor.Pulse(lockObj);
}
catch (System.Threading.SynchronizationLockException /*ex*/)
{
// This lock is unlocked! run and hide ;)
}
Documentos: http://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse.aspx
Hay un problema fundamental con su solicitud. Supongamos que hay un método IsLockHeld () y lo usarías así:
if (not IsLockHeld(someLockObj)) then DoSomething()
Esto no puede funcionar por diseño. Su hilo se puede anular entre la instrucción if () y la llamada al método. Permitir que se ejecute otro subproceso y bloquear el objeto. Ahora se ejecutará DoSomething (), aunque se mantenga el bloqueo.
La única forma de evitar esto es intentar tomar la cerradura y ver si funcionó. Monitor.TryEnter ().
Usted ve el mismo patrón exacto en funcionamiento en Windows. No hay forma de comprobar si un archivo está bloqueado por otro proceso, además de tratar de abrir el archivo. Por la misma razón.
Esto es compatible con .NET 4.5 utilizando Monitor.IsEntered (object) .
Si necesita esto en .NET 4.5, vea la respuesta aceptada de @Dave.
Sin embargo, si necesita esto en .NET 3.5 o 4, puede reemplazar el uso de bloqueos de Monitor para un ReaderWriterLockSlim de la siguiente manera.
Según Joeseph Albahari, esto duplicará aproximadamente su sobrecarga (indiscutible) de bloqueo, pero todavía es muy rápido (est. 40nS). (Descargo de responsabilidad, esa página no indica si las pruebas se realizaron utilizando una política de bloqueo recursivo).
public class SomeClass()
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
public void SomeMethodA()
{
_lock.EnterWriteLock();
try
{
SomeHelperMethod();
//do something that requires lock on _lock
}
finally
{
_lock.ExitWriteLock();
}
}
private void SomeHelperMethod()
{
Debug.Assert(_lock.IsWriteLockHeld);
//do something that requires lock on _lock
}
}
Si crees que es demasiado no productivo, puedes escribir tu propia clase contenedora alrededor de los bloqueos de Monitor. Sin embargo, otras publicaciones han mencionado una bandera booleana que es engañosa ya que la pregunta no es "¿El hilo está sujeto a un hilo?" (que es una pregunta estúpida y peligrosa de preguntar). La pregunta correcta es "¿Está ESTIRADO el bloqueo en ESTE hilo?". Sin embargo, no hay problema, solo haga que el '' marcador '' sea un contador ThreadLocal (para soporte de bloqueo recursivo) y asegúrese de que las actualizaciones se realicen después de Monitor.Enter y antes de Monitor.Exit. Como mejora, afirme que el contador no cae por debajo de 0 en "desbloqueo" ya que eso indicaría llamadas de entrada / salida no balanceadas.
Otra razón para el contenedor sería evitar que alguien se deslice en las llamadas _lock.EnterReadLock. Dependiendo del uso de la clase, en realidad podrían ser útiles, pero un sorprendente número de codificadores no entienden los bloqueos de Reader-Writer.
Ver mi respuesta a una pregunta similar: ¿ probar una cerradura sin adquirirla?