Movatterモバイル変換


[0]ホーム

URL:


Skip to main content

This browser is no longer supported.

Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.

Download Microsoft EdgeMore info about Internet Explorer and Microsoft Edge
Table of contentsExit focus mode

The lock statement - ensure exclusive access to a shared resource

  • 2024-05-07
Feedback

In this article

Thelock 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. Thelock statement ensures that at maximum only one thread executes its body at any moment in time.

Thelock statement takes the following form:

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

The variablex is an expression ofSystem.Threading.Lock type, or areference type. Whenx is known at compile-time to be of the typeSystem.Threading.Lock, it's precisely equivalent to:

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

The object returned byLock.EnterScope() is aref struct that includes aDispose() method. The generatedusing statement ensures the scope is released even if an exception is thrown with the body of thelock statement.

Otherwise, thelock statement is 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);}

Since the code uses atry-finally statement, the lock is released even if an exception is thrown within the body of alock statement.

You can't use theawait expression in the body of alock statement.

Guidelines

Beginning with .NET 9 and C# 13, lock a dedicated object instance of theSystem.Threading.Lock type for best performance. In addition, the compiler issues a warning if a knownLock object is cast to another type and locked. If using an older version of .NET and C#, lock on a dedicated object instance that isn't used for another purpose. 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 instances as lock objects:

  • this, as callers might also lockthis.
  • Type instances, as they might be obtained by thetypeof operator or reflection.
  • string instances, including string literals, as they might beinterned.

Hold a lock for as short time as possible to reduce lock contention.

Example

The following example defines anAccount class that synchronizes access to its privatebalance field by locking on a dedicatedbalanceLock instance. Using the same instance for locking ensures that two different threads can't update thebalance field by calling theDebit orCredit methods simultaneously. The sample uses C# 13 and the newLock object. If you're using an older version of C# or an older .NET library, lock an instance ofobject.

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

For more information, seeThe lock statement section of theC# language specification.

See also

Collaborate with us on GitHub
The source for this content can be found on GitHub, where you can also create and review issues and pull requests. For more information, seeour contributor guide.

Feedback

Was this page helpful?

YesNo

In this article

Was this page helpful?

YesNo