Compartilhar via


A instrução lock – 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 bloqueio é mantido, o thread que mantém o bloqueio pode adquirir e liberar o bloqueio várias vezes. 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 no tempo.

A linguagem C# faz referência a documentos da versão mais recentemente lançada da linguagem C#. Ele também contém a documentação inicial para recursos em visualizações públicas para a próxima versão do idioma.

A documentação identifica qualquer recurso introduzido pela primeira vez nas três últimas versões do idioma ou nas versões prévias públicas atuais.

Dica

Para descobrir quando um recurso foi introduzido pela primeira vez em C#, consulte o artigo sobre o histórico de versão da linguagem C#.

A lock instrução usa o seguinte formulário:

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, ele é exatamente equivalente a:

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

O objeto retornado por Lock.EnterScope() um ref struct que inclui um Dispose() método. A instrução gerada using garante que o escopo seja liberado mesmo se uma exceção for lançada dentro do corpo da lock instrução.

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

Você não pode usar a await expressão no corpo de uma lock instrução.

Diretrizes

Começando com .NET 9 e C# 13, bloqueie uma instância de objeto dedicada do tipo para obter melhor System.Threading.Lock desempenho. O compilador também emitirá um aviso se você converter um objeto conhecido Lock em outro tipo e bloqueá-lo. Se você estiver usando uma versão mais antiga do .NET e do C#, bloqueie uma instância de objeto dedicada que não seja usada para outra finalidade. Evite usar a mesma instância de objeto de bloqueio para diferentes recursos compartilhados, pois isso pode resultar em deadlock ou contenção de bloqueio. Em particular, evite usar as seguintes instâncias como objetos de bloqueio:

  • this, como os chamadores também podem bloquear this.
  • Type instâncias, pois podem ser obtidas pelo operador typeof ou reflexão.
  • instâncias de cadeia de caracteres, incluindo literais de cadeia de caracteres, pois podem ser internadas.

Mantenha um bloqueio pelo menor tempo possível para reduzir a contenção de 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 . O uso da mesma instância para bloqueio garante que dois threads diferentes não possam atualizar o balance campo chamando ou DebitCredit métodos 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 de 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 de instrução lock da especificação da linguagem C#.

Consulte também