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 is van het formulier

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

waarbij x een expressie van een verwijzingstype is. Het is 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

Wanneer u threadtoegang tot een gedeelde resource synchroniseert, vergrendelt u een toegewezen objectexemplaren (bijvoorbeeld private readonly object balanceLock = new object();) of een ander exemplaar dat waarschijnlijk niet wordt gebruikt als een vergrendelingsobject door niet-gerelateerde onderdelen van de code. 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 het kan worden gebruikt door de bellers als een vergrendeling.
  • 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 het balance veld niet tegelijkertijd kan worden bijgewerkt door twee threads die proberen de Debit of Credit methoden tegelijkertijd aan te roepen.

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

C#-taalspecificatie

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

Zie ook