共用方式為


lock 語句 - 確保共用資源的獨佔存取權

語句 lock 會取得指定物件的互斥鎖定、執行語句區塊,然後釋放鎖定。 當持有鎖時,持有鎖的線程可以再次取得並釋放鎖。 任何其他線程都遭到封鎖,無法取得鎖定,並等到鎖定釋放為止。 語句 lock 可確保一個線程在每次每次執行其主體時,最多只能執行一個線程。

語句 lock 的格式如下:

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

變數 x 是 型別的 System.Threading.Lock 表達式,或 參考型別。 當 x 在編譯階段已知為 型 System.Threading.Lock別時,它完全相當於:

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

Lock.EnterScope() 回的物件是 ref struct 包含 方法的 Dispose() 。 產生的 using 語句可確保即使語句主體 lock 擲回例外狀況,仍會釋放範圍。

否則, lock 語句完全相當於:

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 擲回例外狀況,也會釋放鎖定。

您無法在 await 語句主體lock中使用表達式。

指導方針

從 .NET 9 和 C# 13 開始,鎖定類型的專用物件實例以 System.Threading.Lock 獲得最佳效能。 此外,如果已知 Lock 物件轉換成另一個型別並鎖定,編譯程式就會發出警告。 如果使用舊版的 .NET 和 C#,請鎖定未用於其他用途的專用物件實例。 請避免針對不同的共享資源使用相同的鎖定物件實例,因為它可能會導致死結或鎖定爭用。 特別是,請避免使用下列實例作為鎖定物件:

  • this,因為呼叫端也可能鎖定 this
  • Type 實例,因為它們可能由 typeof 運算符或反映取得。
  • 字串實例,包括字串常值,因為它們可能是 實習生

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

範例

下列範例會定義類別Account,其會藉由鎖定專用balanceLock實例,同步存取其私balance用字段。 使用相同的實例進行鎖定,可確保兩個不同的線程無法同時呼叫 DebitCredit 方法來更新balance欄位。 此範例使用 C# 13 和新的 Lock 物件。 如果您使用舊版 C# 或較舊的 .NET 連結庫,請鎖定 的 object實例。

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# 語言規格

如需詳細資訊,請參閱 C# 語言規格lock語句一節。

另請參閱