2012-02-09 17 views
8

Quiero iniciar algunos hilos nuevos cada uno para una operación repetitiva. Pero cuando tal operación ya está en curso, quiero descartar la tarea actual. En mi caso, solo necesito datos muy actuales; los datos eliminados no son un problema.Bloqueo sin bloqueo

En el MSDN encontré la clase Mutex pero, según tengo entendido, aguarda su turno, bloqueando el hilo actual. También quiero preguntarle: ¿Existe algo en el marco .NET ya, que hace lo siguiente:

  1. ya está siendo ejecutado algún método M?
  2. Si es así, return (y que me haga aumentar algunos contador de estadísticas)
  3. Si no es así, se inicia el método M en un nuevo hilo

Respuesta

23

La declaración lock(someObject), que es posible que haya llegado a través, es el azúcar sintáctica alrededor Monitor.Enter y Monitor.Exit.

Sin embargo, si utiliza el monitor de esta manera más detallada, también puede usar Monitor.TryEnter que le permite comprobar si podrá obtener el bloqueo, verificando si alguien más ya lo tiene y está ejecutando el código .

Así que en lugar de esto:

var lockObject = new object(); 

lock(lockObject) 
{ 
    // do some stuff 
} 

probar esto (opción 1):

int _alreadyBeingExecutedCounter; 
var lockObject = new object(); 

if (Monitor.TryEnter(lockObject)) 
{ 
    // you'll only end up here if you got the lock when you tried to get it - otherwise you'll never execute this code. 

    // do some stuff 

    //call exit to release the lock 
    Monitor.Exit(lockObject); 
} 
else 
{ 
    // didn't get the lock - someone else was executing the code above - so I don't need to do any work! 
    Interlocked.Increment(ref _alreadyBeingExecutedCounter); 
} 

(es probable que desee poner un try..finally allí para asegurar la se libera el bloqueo)

o prescindir del bloqueo explícito y hacer esto

(opción 2)

private int _inUseCount; 

public void MyMethod() 
{ 
    if (Interlocked.Increment(ref _inUseCount) == 1) 
    { 
     // do dome stuff  
    } 
    Interlocked.Decrement(ref _inUseCount); 
} 

[Editar: en respuesta a su pregunta sobre this]

No - no utilice this a lock sucesivamente. Cree un objeto de ámbito privado para actuar como su bloqueo.

De lo contrario, tienen este problema potencial:

public class MyClassWithLockInside 
{ 
    public void MethodThatTakesLock() 
    { 
     lock(this) 
     { 
      // do some work 
     } 
    } 
} 

public class Consumer 
{ 
    private static MyClassWithLockInside _instance = new MyClassWithLockInside(); 

    public void ThreadACallsThis() 
    { 
      lock(_instance) 
      { 
       // Having taken a lock on our instance of MyClassWithLockInside, 
       // do something long running 
       Thread.Sleep(6000); 
      } 
    } 

    public void ThreadBCallsThis() 
    { 
     // If thread B calls this while thread A is still inside the lock above, 
     // this method will block as it tries to get a lock on the same object 
     // ["this" inside the class = _instance outside] 
     _instance.MethodThatTakesLock(); 
    } 
} 

En el ejemplo anterior, un código externo ha logrado interrumpir el bloqueo interno de nuestra clase sólo por tomar un bloqueo de algo que era accesible desde el exterior.

Mucho mejor para crear un objeto privado que controlas, y que nadie fuera de tu clase tenga acceso, para evitar este tipo de problemas; esto incluye no usar this o el tipo en sí typeof(MyClassWithLockInside) para bloquear.

+0

¿Sería 'this' ser un cuadro LockObject adecuada? – DerMike

+2

¿Por qué el voto a favor? –

+1

Aceptado para la opción 2 – DerMike

1

Es posible encontrar información útil sobre el siguiente site (pdf es también downlaodable - recientemente lo descargué yo mismo). El Adavnced threading suspender y reanudar o abortar capítulos tal vez lo que está en inetrested.

+0

me gustaría leer el resto del artículo, he encontrado que es indispensable, http://www.albahari.com/threading/ desde el principio – Robert

2

Una opción sería trabajar con un centinela reentrada:

se podría definir un campo int (inicializar con 0) y actualizarlo a través de Interlocked.Increment al entrar en el método y sólo procederá si es 1. Al final solo haz un Interlocked.Decrement.

Otra opción:

Desde su descripción parece que tiene un productor-consumidor-Escenario ...

Para este caso podría ser útil usar algo así como BlockingCollection ya que es seguro para subprocesos y sobre todo bloqueo de libre ...

Otra opción sería utilizar ConcurrentQueue o ConcurrentStack ...

+0

Buen punto. Gracias. ¿Cómo se vería el lado positivo? – DerMike

+0

@DerMike Les pondría un ejemplo pero necesitaría más información ... básicamente estas colecciones tienen métodos 'Try' que no se bloquean ... consulte también mi actualización anterior sobre el uso de' Interlocked' ... – Yahia

0

que puedes usar operaciones atómicas de clase Interlocked - para un mejor rendimiento - ya que en realidad no usará sincronizaciones a nivel de sistema (cualquier primitiva "estándar" lo necesita, e involucra gastos generales de llamadas del sistema). // mutex sin reentrada simple sin propiedad, fácil de rehacer para admitir // estas características (simplemente configure el propietario después de adquirir el bloqueo (compare la referencia del hilo con Thread.CurrentThread por ejemplo) y verifique la identidad correspondiente, agregue el contador para la reentrada) // no se puede usar bool porque no es compatible con CompareExchange private int lock;

public bool TryLock() 
{ 
    //if (Interlocked.Increment(ref _inUseCount) == 1)  
    //that kind of code is buggy - since counter can change between increment return and 
    //condition check - increment is atomic, this if - isn't.  
    //Use CompareExchange instead 
    //checks if 0 then changes to 1 atomically, returns original value 
    //return true if thread succesfully occupied lock 
    return CompareExchange(ref lock, 1, 0)==0; 
    return false; 

} 
public bool Release() 
{ 
    //returns true if lock was occupied; false if it was free already 
    return CompareExchange(ref lock, 0, 1)==1; 
}