lock 陳述式 - 確保共用資源的獨佔存取權

lock 陳述式會取得指定物件的互斥鎖定、執行陳述式區塊,然後釋放鎖定。 持有鎖定時,持有鎖定的執行緒可以再次取得並釋放鎖定。 其他執行緒將無法取得鎖定,並將等待直到釋放鎖定為止。 lock 陳述式可確保任何時候最多只能有一個執行緒執行其主體。

lock 陳述式格式為

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

x參考型別的運算式。 其完全等同於

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 陳述式的主體內擲回例外狀況,也會釋放鎖定。

您不能在 lock 陳述式主體中使用 await 運算式

指導方針

當您同步處理執行緒對共用資源的存取時,請鎖定專用物件執行個體 (例如 private readonly object balanceLock = new object();) 或另一個不太可能由程式碼不相關的部分用作鎖定物件的執行個體。 避免對不同的共用資源使用相同的鎖定物件執行個體,因為其可能導致鎖死或鎖定爭用。 尤其要避免使用下列執行個體當作鎖定物件:

  • this,呼叫者可能會將其作為鎖定。
  • Type 執行個體,因為這些執行個體可能會由 typeof 運算子或反映取得。
  • 字串執行個體 (包括字串常值),因為這些執行個體可能會暫留

盡可能縮短鎖定的時間,以減少鎖定爭用。

範例

下列範例會定義 Account 類別,該類別會透過鎖定專用的 balanceLock 執行個體來同步對其私用 balance 欄位的存取。 使用相同的執行個體進行鎖定,可確保嘗試同時呼叫 DebitCredit 方法的兩個執行緒無法同時更新 balance 欄位。

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# 語言規格lock 陳述式一節。

另請參閱