Partilhar via


A 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 um cadeado está segurado, o fio que o segura pode adquirir e libertar o cadeado várias vezes. Qualquer outro thread é impedido de adquirir o bloqueio e aguarda até que o bloqueio seja liberado. A lock instrução assegura que, no máximo, apenas um thread executa o seu corpo em qualquer momento.

A referência da linguagem C# documenta a versão mais recentemente lançada da linguagem C#. Contém também documentação inicial para funcionalidades em pré-visualizações públicas para o próximo lançamento linguístico.

A documentação identifica qualquer funcionalidade introduzida pela primeira vez nas últimas três versões da língua ou em pré-visualizações públicas atuais.

Sugestão

Para saber quando uma funcionalidade foi introduzida pela primeira vez em C#, consulte o artigo sobre o histórico de versões da linguagem C#.

A lock declaração assume a seguinte forma:

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

A variável x é uma expressão de tipo, ou um System.Threading.Lock de referência. Quando o compilador sabe que x é do tipo System.Threading.Lock, é precisamente equivalente a:

using (x.EnterScope())
{
    // Your code...
}

O objeto retornado por Lock.EnterScope() é um ref struct que inclui um Dispose() método. A declaração gerada using garante que o âmbito é divulgado mesmo que uma exceção seja incluída no corpo da lock declaração.

Caso contrário, a lock afirmação é 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.

Orientações

A partir de .NET 9 e C# 13, bloqueie uma instância dedicada de objeto do System.Threading.Lock tipo para obter o melhor desempenho. O compilador também emite um aviso se converter um objeto conhecido Lock para outro tipo e o bloquear. Se estiveres a usar uma versão mais antiga de .NET e C#, bloqueia uma instância de objeto dedicada que não seja usada para outro propósito. 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, uma vez que os autores de chamadas também podem bloquear this.
  • Type instâ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 dois threads diferentes não possam atualizar o balance campo chamando os Debit métodos or Credit simultaneamente. O exemplo usa C# 13 e o novo Lock objeto. Se você estiver usando uma versão mais antiga do C# ou uma biblioteca .NET mais antiga, bloqueie uma instância do object.

using System;
using System.Threading.Tasks;

public class Account
{
    // Use `object` in versions earlier than C# 13
    private readonly System.Threading.Lock _balanceLock = new();
    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#.

Ver também