Anweisung „lock“: Stellt den exklusiven Zugriff auf eine freigegebene Ressource sicher.

Die lock-Anweisung ruft die Sperre für gegenseitigen Ausschluss für ein bestimmtes Objekt ab, führt einen Anweisungsblock aus und hebt die Sperre anschließend auf. Während eine Sperre aufrechterhalten wird, kann der Thread, der die Sperre aufrechterhält, die Sperre abrufen und aufheben. Für jeden anderen Thread wird das Abrufen der Sperre blockiert, und die Sperre wartet auf die Aufhebung. Die lock-Anweisung stellt sicher, dass maximal ein Thread seinen Text zu jedem beliebigen Zeitpunkt ausführt.

Die lock-Anweisung weist folgendes Format auf:

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

x entspricht einem Ausdruck eines Verweistyps. Dieser entspricht exakt Folgendem:

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

Da eine try-finally-Anweisung in diesem Code verwendet wird, wird die Sperre selbst dann aufgehoben, wenn eine Ausnahme innerhalb des Texts einer lock-Anweisung ausgelöst wird.

Sie können den await-Ausdruck nicht im Text einer lock-Anweisung verwenden.

Richtlinien

Wenn Sie den Threadzugriff auf eine freigegebene Ressource synchronisieren, sperren Sie eine dedizierte Objektinstanz (z.B. private readonly object balanceLock = new object();) oder eine andere Instanz, die wahrscheinlich nicht von anderen Teilen des Codes als lock-Objekt verwendet wird. Vermeiden Sie, die gleiche lock-Objektinstanz für verschiedene freigegebene Ressourcen zu verwenden, da dies zu einem Deadlock oder Sperrkonflikt führen kann. Vermeiden Sie insbesondere die Verwendung der folgenden Instanzen als Sperrobjekte:

  • this – kann von den Aufrufern als Sperre verwendet werden.
  • Type-Instanzen: Diese können vom typeof-Operator oder durch Reflexion abgerufen werden.
  • Zeichenfolgeninstanzen, einschließlich Zeichenfolgenliteralen: Diese können internalisiert sein.

Die Dauer von Sperren sollte so kurz wie möglich sein, um Sperrungskonflikte zu vermindern.

Beispiel

Im folgenden Beispiel wird eine Account-Klasse definiert, die den Zugriff auf das private balance-Feld synchronisiert, indem eine dedizierte balanceLock-Instanz gesperrt wird. Durch Verwendung der gleichen Instanz für die Sperre wird sichergestellt, dass das Feld balance nicht von zwei Threads gleichzeitig aktualisiert werden kann, die zur gleichen Zeit versuchen, die Methoden Debit oder Credit aufzurufen.

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#-Sprachspezifikation

Weitere Informationen finden Sie im Abschnitt Die lock-Anweisung der C#-Sprachspezifikation.

Weitere Informationen