Buscar..


Sintaxis

  • bloqueo (obj) {}

Observaciones

Usando la instrucción de lock , puede controlar el acceso de diferentes hilos al código dentro del bloque de código. Se usa comúnmente para prevenir condiciones de carrera, por ejemplo, varios subprocesos que leen y eliminan elementos de una colección. Como el bloqueo hace que los subprocesos esperen a que otros suban para salir de un bloque de código, puede causar retrasos que podrían resolverse con otros métodos de sincronización.

MSDN

La palabra clave de bloqueo marca un bloque de declaración como una sección crítica al obtener el bloqueo de exclusión mutua para un objeto determinado, ejecutar una declaración y luego liberar el bloqueo.

La palabra clave de bloqueo garantiza que un subproceso no ingrese a una sección crítica del código mientras que otro subproceso está en la sección crítica. Si otro hilo intenta ingresar un código bloqueado, esperará, bloqueará, hasta que se libere el objeto.

La mejor práctica es definir un objeto privado para bloquear, o una variable de objeto estática privada para proteger los datos comunes a todas las instancias.


En C # 5.0 y versiones posteriores, la instrucción de lock es equivalente a:

bool lockTaken = false;
try 
{
    System.Threading.Monitor.Enter(refObject, ref lockTaken);
    // code 
}
finally 
{
    if (lockTaken)
        System.Threading.Monitor.Exit(refObject);
}

Para C # 4.0 y anteriores, la declaración de lock es equivalente a:

System.Threading.Monitor.Enter(refObject);
try 
{
    // code
}
finally 
{
     System.Threading.Monitor.Exit(refObject);
}

Uso simple

El uso común de la lock es una sección crítica.

En el siguiente ejemplo, se supone que ReserveRoom debe llamarse desde diferentes subprocesos. La sincronización con lock es la forma más sencilla de evitar las condiciones de carrera aquí. El cuerpo del método está rodeado de un lock que garantiza que dos o más subprocesos no puedan ejecutarlo simultáneamente.

public class Hotel
{
    private readonly object _roomLock = new object();

    public void ReserveRoom(int roomNumber)
    {
        // lock keyword ensures that only one thread executes critical section at once
        // in this case, reserves a hotel room of given number
        // preventing double bookings
        lock (_roomLock)
        {
            // reserve room logic goes here
        }
    }
}

Si un hilo llega a lock bloque bloqueado mientras otro se ejecuta dentro de él, el primero esperará a otro para salir del bloque.

La mejor práctica es definir un objeto privado para bloquear, o una variable de objeto estática privada para proteger los datos comunes a todas las instancias.

Lanzar excepción en una sentencia de bloqueo

El siguiente código liberará el bloqueo. No habrá problema. Detrás de la escena de bloqueo de escenas funciona como try finally

lock(locker)
{
    throw new Exception();
}

Se puede ver más en la especificación de C # 5.0 :

Una declaración de lock de la forma

lock (x) ...

donde x es una expresión de un tipo de referencia , es exactamente equivalente a

bool __lockWasTaken = false;
try {
    System.Threading.Monitor.Enter(x, ref __lockWasTaken);
    ...
}
finally {
    if (__lockWasTaken) System.Threading.Monitor.Exit(x);
}

excepto que x sólo se evalúa una vez.

Volver en una declaración de bloqueo

El siguiente código liberará el bloqueo.

lock(locker)
{
    return 5;
}

Para una explicación detallada, se recomienda esta respuesta SO .

Usando instancias de Object para bloqueo

Cuando se utiliza la instrucción de lock incorporada de C #, se necesita una instancia de algún tipo, pero su estado no importa. Una instancia de object es perfecta para esto:

public class ThreadSafe {
  private static readonly object locker = new object();


  public void SomeThreadSafeMethod() {
    lock (locker) {
      // Only one thread can be here at a time.
    }
  }
}

NB . las instancias de Type no deben usarse para esto (en el código anterior a typeof(ThreadSafe) ) porque las instancias de Type se comparten entre los typeof(ThreadSafe) y, por lo tanto, la extensión del bloqueo puede incluir código que no debería (por ejemplo, si ThreadSafe está cargado en dos AppDomains en el mismo proceso y luego el bloqueo en su instancia de Type se bloquearían mutuamente).

Anti-patrones y gotchas

Bloqueo en una variable local / asignada a la pila

Una de las falacias al usar el lock es el uso de objetos locales como casillero en una función. Dado que estas instancias de objetos locales diferirán en cada llamada de la función, el lock no funcionará como se esperaba.

List<string> stringList = new List<string>();

public void AddToListNotThreadSafe(string something)
{
    // DO NOT do this, as each call to this method 
    // will lock on a different instance of an Object.
    // This provides no thread safety, it only degrades performance.
    var localLock = new Object();
    lock(localLock)
    {
        stringList.Add(something);
    }
}

// Define object that can be used for thread safety in the AddToList method
readonly object classLock = new object();

public void AddToList(List<string> stringList, string something)
{
    // USE THE classLock instance field to achieve a 
    // thread-safe lock before adding to stringList
    lock(classLock)
    {
        stringList.Add(something);
    }
}

Suponiendo que el bloqueo restringe el acceso al objeto de sincronización en sí

Si un subproceso llama: el lock(obj) y otro subproceso llama a obj.ToString() segundo subproceso no se va a bloquear.

object obj = new Object();
 
public void SomeMethod()
{
     lock(obj)
    {
       //do dangerous stuff 
    }
 }

 //Meanwhile on other tread 
 public void SomeOtherMethod()
 {
   var objInString = obj.ToString(); //this does not block
 }

Esperando que las subclases sepan cuándo bloquear

A veces, las clases base están diseñadas de tal manera que sus subclases deben usar un bloqueo al acceder a ciertos campos protegidos:

public abstract class Base
{
    protected readonly object padlock;
    protected readonly List<string> list;

    public Base()
    {
        this.padlock = new object();
        this.list = new List<string>();
    }

    public abstract void Do();
}

public class Derived1 : Base
{
    public override void Do()
    {
        lock (this.padlock)
        {
            this.list.Add("Derived1");
        }
    }
}

public class Derived2 : Base
{
    public override void Do()
    {
        this.list.Add("Derived2"); // OOPS! I forgot to lock!
    }
}

Es mucho más seguro encapsular el bloqueo mediante el uso de un método de plantilla :

public abstract class Base
{
    private readonly object padlock; // This is now private
    protected readonly List<string> list;

    public Base()
    {
        this.padlock = new object();
        this.list = new List<string>();
    }

    public void Do()
    {
        lock (this.padlock) {
            this.DoInternal();
        }
    }

    protected abstract void DoInternal();
}

public class Derived1 : Base
{
    protected override void DoInternal()
    {
        this.list.Add("Derived1"); // Yay! No need to lock
    }
}

El bloqueo en una variable ValueType en caja no se sincroniza

En el siguiente ejemplo, una variable privada está encuadrada implícitamente ya que se suministra como un argumento de object a una función, esperando que un recurso de monitor se bloquee. El boxeo ocurre justo antes de llamar a la función IncInSync, por lo que la instancia en caja corresponde a un objeto de pila diferente cada vez que se llama a la función.

public int Count { get; private set; }

private readonly int counterLock = 1;

public void Inc()
{
    IncInSync(counterLock);
}

private void IncInSync(object monitorResource)
{
    lock (monitorResource)
    {
        Count++;
    }
}

El boxeo se produce en la función Inc :

BulemicCounter.Inc:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldarg.0     
IL_0003:  ldfld       UserQuery+BulemicCounter.counterLock
IL_0008:  box         System.Int32**
IL_000D:  call        UserQuery+BulemicCounter.IncInSync
IL_0012:  nop         
IL_0013:  ret         

No significa que no se pueda utilizar un ValueType en caja para el bloqueo del monitor:

private readonly object counterLock = 1;

Ahora el boxeo ocurre en el constructor, lo cual está bien para bloquear:

IL_0001:  ldc.i4.1    
IL_0002:  box         System.Int32
IL_0007:  stfld       UserQuery+BulemicCounter.counterLock

Usar cerraduras innecesariamente cuando existe una alternativa más segura

Un patrón muy común es usar una List o Dictionary privado en una clase segura para subprocesos y bloquear cada vez que se accede a él:

public class Cache
{
    private readonly object padlock;
    private readonly Dictionary<string, object> values;

    public WordStats()
    {
        this.padlock = new object();
        this.values = new Dictionary<string, object>();
    }
    
    public void Add(string key, object value)
    {
        lock (this.padlock)
        {
            this.values.Add(key, value);
        }
    }

    /* rest of class omitted */
}

Si hay varios métodos para acceder al diccionario de values , el código puede ser muy largo y, lo que es más importante, bloquear todo el tiempo oculta su intención . El bloqueo también es muy fácil de olvidar y la falta de un bloqueo adecuado puede causar errores muy difíciles de encontrar.

Mediante el uso de un ConcurrentDictionary , podemos evitar el bloqueo por completo:

public class Cache
{
    private readonly ConcurrentDictionary<string, object> values;

    public WordStats()
    {
        this.values = new ConcurrentDictionary<string, object>();
    }
    
    public void Add(string key, object value)
    {
        this.values.Add(key, value);
    }

    /* rest of class omitted */
}

El uso de colecciones simultáneas también mejora el rendimiento, ya que todas emplean técnicas de bloqueo en cierta medida.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow