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 je ve formuláři.

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

where x je výraz typu odkazu. Je to přesně ekvivalentem

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

Při synchronizaci přístupu vlákna ke sdílenému prostředku zamkněte instanci vyhrazeného objektu (například private readonly object balanceLock = new object();) nebo jinou instanci, která není pravděpodobně použita jako objekt zámku nesouvisejícími částmi kódu. 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í ho můžou používat jako zámek.
  • 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 balance pole nelze aktualizovat současně dvěma vlákny, které se pokoušejí volat Debit nebo Credit metody současně.

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

specifikace jazyka C#

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

Viz také