Die lock-Anweisung 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 hat folgende Form:

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

Die Variable x ist ein Ausdruck vom Typ System.Threading.Lock oder ein Verweistyp. Wenn von x zur Kompilierzeit bekannt ist, dass der Typ System.Threading.Lock lautet, ist es identisch mit:

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

Das von Lock.EnterScope() zurückgegebene Objekt ist ein ref struct-Objekt, das eine Dispose()-Methode enthält. Die generierte using-Anweisung stellt sicher, dass der Bereich auch dann freigegeben wird, wenn eine Ausnahme mit dem Körper der lock-Anweisung ausgelöst wird.

Andernfalls entspricht die lock-Anweisung genau 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

Ab .NET 9 und C# 13 sollten Sie eine dedizierte Objektinstanz vom Typ System.Threading.Lock für eine optimale Leistung sperren. Darüber hinaus gibt der Compiler eine Warnung aus, wenn ein bekanntes Lock-Objekt in einen anderen Typ umgewandelt und gesperrt wird. Wenn Sie eine ältere Version von .NET und C# verwenden, sperren Sie eine dedizierte Objektinstanz, die für keinen anderen Zweck 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, da Anrufer möglicherweise auch this sperren.
  • 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 derselben Instanz zum Sperren wird sichergestellt, dass zwei verschiedene Threads das Feld balance nicht gleichzeitig durch einen Aufruf der Methoden Debit oder Credit aktualisieren können. Im Beispiel werden C# 13 und das neue Lock-Objekt verwendet. Wenn Sie eine ältere Version von C# oder eine ältere .NET-Bibliothek verwenden, sperren Sie eine Instanz von 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#-Sprachspezifikation

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

Weitere Informationen