Инструкция lock. (Справочник по C#)lock statement (C# reference)

Оператор lock получает взаимоисключающую блокировку заданного объекта перед выполнением определенных операторов, а затем снимает блокировку.The lock statement acquires the mutual-exclusion lock for a given object, executes a statement block, and then releases the lock. Во время блокировки поток, удерживающий блокировку, может снова поставить и снять блокировку.While a lock is held, the thread that holds the lock can again acquire and release the lock. Любой другой поток не может получить блокировку и ожидает ее снятия.Any other thread is blocked from acquiring the lock and waits until the lock is released.

Оператор lock имеет формуThe lock statement is of the form

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

Здесь x — это выражение ссылочного типа.where x is an expression of a reference type. Оно является точным эквивалентомIt's precisely equivalent to

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

Так как в коде используется блок try... finally, блокировка освобождается, даже если возникает исключение в теле оператора lock.Since the code uses a try...finally block, the lock is released even if an exception is thrown within the body of a lock statement.

Нельзя использовать оператор await в теле оператора lock.You can't use the await operator in the body of a lock statement.

РекомендацииGuidelines

При синхронизации доступа потоков к общему ресурсу блокируйте выделенный экземпляр объекта (например, private readonly object balanceLock = new object();) или другой экземпляр, который, скорее всего, не будет использоваться как объект блокировки другими частями кода.When you synchronize thread access to a shared resource, lock on a dedicated object instance (for example, private readonly object balanceLock = new object();) or another instance that is unlikely to be used as a lock object by unrelated parts of the code. Не используйте один и тот же экземпляр объекта блокировки для разных общих ресурсов: это может привести к взаимоблокировке или состязанию при блокировке.Avoid using the same lock object instance for different shared resources, as it might result in deadlock or lock contention. В особенности избегайте использования следующих объектов в качестве объектов блокировки:In particular, avoid using the following as lock objects:

  • this, так как он может использоваться вызывающими объектами как блокировка;this, as it might be used by the callers as a lock.
  • экземпляров Type, так как их может получать оператор typeof или отражение;Type instances, as those might be obtained by the typeof operator or reflection.
  • строковых экземпляров, включая строковые литералы, так как они могут быть интернированы.string instances, including string literals, as those might be interned.

Удерживайте блокировку в течение максимально короткого времени, чтобы сократить число конфликтов при блокировке.Hold a lock for as short time as possible to reduce lock contention.

ПримерExample

В следующем примере определяется класс Account, который синхронизирует доступ к закрытому полю balance путем блокировки выделенного экземпляра balanceLock.The following example defines an Account class that synchronizes access to its private balance field by locking on a dedicated balanceLock instance. Использование того же экземпляра для блокировки гарантирует, что поле balance не смогут одновременно обновить два потока, пытающиеся вызвать методы Debit или Credit одновременно.Using the same instance for locking ensures that the balance field cannot be updated simultaneously by two threads attempting to call the Debit or Credit methods simultaneously.

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#C# language specification

Дополнительные сведения см. в разделе об инструкции lock в документации Предварительная спецификация C# 6.0.For more information, see The lock statement section of the C# language specification.

См. такжеSee also