Příkaz lock – zajištění výhradního přístupu ke sdílenému prostředku

Příkaz lock získá zámek vzájemného vyloučení pro daný objekt, spustí blok příkazu a pak uvolní zámek. Zatímco se zámek drží, vlákno, které zámek obsahuje, může zámek znovu získat a uvolnit. Jakékoli jiné vlákno se zablokuje v získání zámku a počká, až se zámek uvolní. Tento lock příkaz zajistí, že v každém okamžiku spustí tělo maximálně jednoho vlákna.

Příkaz lock má následující formu:

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

Proměnná x je výraz typu System.Threading.Lock nebo odkazový typ. Pokud x je známo, že v době kompilace má být typ System.Threading.Lock, je přesně ekvivalentní:

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

Objekt vrácený Lock.EnterScope() je objekt ref struct , který zahrnuje metodu Dispose() . Vygenerovaný using příkaz zajistí uvolnění oboru i v případě, že je vyvolán výjimka s tělem lock příkazu.

lock V opačném případě je příkaz přesně ekvivalentní:

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

Vzhledem k tomu, že kód používá try-finally příkaz, zámek se uvolní i v případě, že je v těle lock příkazu vyvolán výjimka.

Výraz nelze použít await v textu lock příkazu.

Pokyny

Počínaje rozhraním .NET 9 a C# 13 zamkněte vyhrazenou instanci objektu System.Threading.Lock typu pro zajištění nejlepšího výkonu. Kromě toho kompilátor vydá upozornění, pokud je známý Lock objekt přetypován na jiný typ a uzamčen. Pokud používáte starší verzi .NET a C#, zamkněte vyhrazenou instanci objektu, která se nepoužívá k jinému účelu. Nepoužívejte stejnou instanci objektu uzamčení pro různé sdílené prostředky, protože může vést k zablokování nebo kolizí uzamčení. Zejména nepoužívejte následující instance jako objekty zámků:

  • this, protože volající mohou také uzamknout this.
  • Type instance, protože mohou být získány typeof operátor nebo reflexe.
  • instance řetězců, včetně řetězcových literálů, protože se můžou prolínat.

Držte zámek co nejkratší dobu, abyste snížili kolize zámků.

Příklad

Následující příklad definuje Account třídu, která synchronizuje přístup k jeho privátnímu balance poli uzamčením vyhrazené balanceLock instance. Použití stejné instance pro uzamčení zajišťuje, že dvě různá vlákna nemohou aktualizovat balance pole voláním Debit nebo Credit metod současně. Ukázka používá C# 13 a nový Lock objekt. Pokud používáte starší verzi jazyka C# nebo starší knihovnu .NET, zamkněte instanci 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));
            }
        }
    }
}

specifikace jazyka C#

Další informace naleznete v části Příkaz lock specifikace jazyka C#.

Viz také