Share via


De vergrendelingsinstructie: exclusieve toegang tot een gedeelde resource garanderen

De lock instructie verkrijgt de wederzijdse uitsluitingsvergrendeling voor een bepaald object, voert een instructieblok uit en geeft vervolgens de vergrendeling vrij. Terwijl een vergrendeling wordt vastgehouden, kan de thread die de vergrendeling bevat, de vergrendeling opnieuw verkrijgen en vrijgeven. Elke andere thread wordt geblokkeerd voor het verkrijgen van de vergrendeling en wacht totdat de vergrendeling wordt vrijgegeven. De lock instructie zorgt ervoor dat maximaal één thread de hoofdtekst op elk moment uitvoert.

De lock instructie heeft de volgende vorm:

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

De variabele x is een expressie van System.Threading.Lock het type of een verwijzingstype. Wanneer x wordt bekend dat het type compileertijd System.Threading.Lockheeft, is het precies gelijk aan:

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

Het object dat wordt geretourneerd door Lock.EnterScope() is een ref struct methode die een Dispose() methode bevat. De gegenereerde using instructie zorgt ervoor dat het bereik wordt vrijgegeven, zelfs als er een uitzondering wordt gegenereerd met de hoofdtekst van de lock instructie.

Anders is de lock instructie precies gelijk aan:

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

Omdat de code gebruikmaakt van een try-finally instructie, wordt de vergrendeling vrijgegeven, zelfs als er een uitzondering wordt gegenereerd in de hoofdtekst van een lock instructie.

U kunt de await expressie niet gebruiken in de hoofdtekst van een lock instructie.

Richtlijnen

Vanaf .NET 9 en C# 13 vergrendelt u een toegewezen objectexemplaren van het type voor de System.Threading.Lock beste prestaties. Bovendien geeft de compiler een waarschuwing uit als een bekend Lock object naar een ander type wordt gecast en vergrendeld. Als u een oudere versie van .NET en C# gebruikt, vergrendelt u een toegewezen objectexemplaren die niet voor een ander doel worden gebruikt. Vermijd het gebruik van hetzelfde exemplaar van het vergrendelingsobject voor verschillende gedeelde resources, omdat dit kan leiden tot impasse- of vergrendelingsconflicten. Vermijd met name het gebruik van de volgende exemplaren als vergrendelingsobjecten:

  • this, omdat bellers ook kunnen vergrendelen this.
  • Type exemplaren, zoals ze kunnen worden verkregen door de operator of weerspiegeling van het type .
  • tekenreeksexemplaren, inclusief letterlijke tekenreeksen, omdat ze mogelijk worden geïnterneerd.

Houd een vergrendeling zo kort mogelijk vast om conflicten met de vergrendeling te verminderen.

Opmerking

In het volgende voorbeeld wordt een Account klasse gedefinieerd waarmee de toegang tot het privéveld balance wordt gesynchroniseerd door een toegewezen balanceLock exemplaar te vergrendelen. Als u hetzelfde exemplaar gebruikt voor het vergrendelen, zorgt u ervoor dat twee verschillende threads het veld niet kunnen bijwerken door het balanceDebit of Credit de methoden tegelijkertijd aan te roepen. In het voorbeeld wordt C# 13 en het nieuwe Lock object gebruikt. Als u een oudere versie van C# of een oudere .NET-bibliotheek gebruikt, vergrendelt u een exemplaar van 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));
            }
        }
    }
}

C#-taalspecificatie

Zie de sectie Vergrendelingsinstructie van de C#-taalspecificatie voor meer informatie.

Zie ook