instrucción lock: asegúrese del acceso exclusivo a un recurso compartido.

La instrucción lock adquiere el bloqueo de exclusión mutua de un objeto determinado, ejecuta un bloque de instrucciones y luego libera el bloqueo. Mientras se mantiene un bloqueo, el subproceso que lo mantiene puede volver a adquirir y liberar el bloqueo. Ningún otro subproceso puede adquirir el bloqueo y espera hasta que se libera. La lock instrucción garantiza que un único subproceso tenga acceso exclusivo a ese objeto.

La instrucción lock tiene el formato

lock (x)
{
    // Your code...
}

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

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

Puesto que el código usa un bloque try... finally, el bloqueo se libera aunque se produzca una excepción dentro del cuerpo de una instrucción lock.

No se puede usar el operador await en el cuerpo de una instrucción lock.

Instrucciones

Al sincronizar el acceso del subproceso al recurso compartido, bloquee una instancia dedicada de objeto (por ejemplo, private readonly object balanceLock = new object();) u otra instancia cuyo empleo como objeto de bloqueo sea poco probable por parte de elementos no relacionados del código. Evite el uso de la misma instancia de objeto de bloqueo para distintos recursos compartidos, ya que se podría producir un interbloqueo o una contención de bloqueo. En concreto, evite usar los siguientes tipos como objetos de bloqueo:

  • this, porque los autores de llamadas podrían usarlo como un bloqueo.
  • Type instancias, ya que esos objetos pueden obtenerse mediante el operador typeof o la reflexión.
  • instancias de cadena, incluidos los literales de cadena, ya que los literales de cadena pueden ser internados.

Mantenga el bloqueo durante el menor tiempo posible para reducir la contención de bloqueo.

Ejemplo

En el ejemplo siguiente se define una clase Account que sincroniza el acceso a su campo privado balance mediante el bloqueo de una instancia dedicada balanceLock. El uso de la misma instancia para el bloqueo garantiza que el balance campo no se puede actualizar simultáneamente mediante dos subprocesos que intentan llamar a los Debit métodos o Credit simultáneamente.

using System;
using System.Threading.Tasks;

public class Account
{
    private readonly object balanceLock = new object();
    private decimal balance;

    public Account(decimal initialBalance) => balance = initialBalance;

    public decimal Debit(decimal amount)
    {
        if (amount < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(amount), "The debit amount cannot be negative.");
        }

        decimal appliedAmount = 0;
        lock (balanceLock)
        {
            if (balance >= amount)
            {
                balance -= amount;
                appliedAmount = amount;
            }
        }
        return appliedAmount;
    }

    public void Credit(decimal amount)
    {
        if (amount < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(amount), "The credit amount cannot be negative.");
        }

        lock (balanceLock)
        {
            balance += amount;
        }
    }

    public decimal GetBalance()
    {
        lock (balanceLock)
        {
            return balance;
        }
    }
}

class AccountTest
{
    static async Task Main()
    {
        var account = new Account(1000);
        var tasks = new Task[100];
        for (int i = 0; i < tasks.Length; i++)
        {
            tasks[i] = Task.Run(() => Update(account));
        }
        await Task.WhenAll(tasks);
        Console.WriteLine($"Account's balance is {account.GetBalance()}");
        // Output:
        // Account's balance is 2000
    }

    static void Update(Account account)
    {
        decimal[] amounts = { 0, 2, -3, 6, -2, -1, 8, -5, 11, -6 };
        foreach (var amount in amounts)
        {
            if (amount >= 0)
            {
                account.Credit(amount);
            }
            else
            {
                account.Debit(Math.Abs(amount));
            }
        }
    }
}

Especificación del lenguaje C#

Para más información, consulte la sección sobre la instrucción lock de la especificación del lenguaje C#.

Vea también