Declaração de bloqueio - Garantir acesso exclusivo a um recurso compartilhado

A lock instrução adquire o bloqueio de exclusão mútua para um determinado objeto, executa um bloco de instrução e, em seguida, libera o bloqueio. Enquanto uma fechadura é segurada, a rosca que segura a fechadura pode novamente adquirir e liberar a fechadura. Qualquer outro thread é impedido de adquirir o bloqueio e aguarda até que o bloqueio seja liberado. A lock instrução garante que, no máximo, apenas um thread execute seu corpo a qualquer momento.

A lock declaração é da forma

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

onde x é uma expressão de um tipo de referência. É precisamente 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);
}

Como o código usa uma try-finally instrução, o bloqueio é liberado mesmo se uma exceção for lançada dentro do corpo de uma lock instrução.

Não é possível usar a await expressão no corpo de uma lock instrução.

Diretrizes

Quando você sincroniza o acesso de thread a um recurso compartilhado, bloqueie uma instância de objeto dedicada (por exemplo, private readonly object balanceLock = new object();) ou outra instância que provavelmente não será usada como um objeto de bloqueio por partes não relacionadas do código. Evite usar a mesma instância de objeto de bloqueio para diferentes recursos compartilhados, pois isso pode resultar em impasse ou contenção de bloqueio. Em particular, evite usar as seguintes instâncias como objetos de bloqueio:

  • this, pois pode ser usado pelos chamadores como um bloqueio.
  • Typeinstâncias, como podem ser obtidas pelo tipo de operador ou reflexão.
  • ocorrências de cadeia de caracteres, incluindo literais de cadeia de caracteres, como eles podem ser internados.

Segure um cadeado pelo menor tempo possível para reduzir a contenção do bloqueio.

Exemplo

O exemplo a seguir define uma Account classe que sincroniza o acesso ao seu campo privado balance bloqueando uma instância dedicada balanceLock . Usar a mesma instância para bloqueio garante que o balance campo não possa ser atualizado simultaneamente por dois threads que tentam chamar os Debit métodos ou Credit simultaneamente.

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));
            }
        }
    }
}

Especificação da linguagem C#

Para obter mais informações, consulte a seção A instrução de bloqueio da especificação da linguagem C#.

Consulte também