Оператор 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 keyword in the body of a lock statement.

ПримечанияRemarks

При синхронизации доступа потоков к общему ресурсу блокируйте выделенный экземпляр объекта (например, 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.

Пример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)
    {
        lock (balanceLock)
        {
            if (balance >= amount)
            {
                Console.WriteLine($"Balance before debit :{balance, 5}");
                Console.WriteLine($"Amount to remove     :{amount, 5}");
                balance = balance - amount;
                Console.WriteLine($"Balance after debit  :{balance, 5}");
                return amount;
            }
            else
            {
                return 0;
            }
        }
    }

    public void Credit(decimal amount)
    {
        lock (balanceLock)
        {
            Console.WriteLine($"Balance before credit:{balance, 5}");
            Console.WriteLine($"Amount to add        :{amount, 5}");
            balance = balance + amount;
            Console.WriteLine($"Balance after credit :{balance, 5}");
        }
    }
}

class AccountTest
{
    static void Main()
    {
        var account = new Account(1000);
        var tasks = new Task[100];
        for (int i = 0; i < tasks.Length; i++)
        {
            tasks[i] = Task.Run(() => RandomlyUpdate(account));
        }
        Task.WaitAll(tasks);
    }

    static void RandomlyUpdate(Account account)
    {
        var rnd = new Random();
        for (int i = 0; i < 10; i++)
        {
            var amount = rnd.Next(1, 100);
            bool doCredit = rnd.NextDouble() < 0.5;
            if (doCredit)
            {
                account.Credit(amount);
            }
            else
            {
                account.Debit(amount);
            }
        }
    }
}

Спецификация языка C#C# language specification

Дополнительные сведения см. в спецификации языка C#.For more information, see the C# Language Specification. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.The language specification is the definitive source for C# syntax and usage.

См. такжеSee also