Bagikan melalui


Pernyataan kunci - memastikan akses eksklusif ke sumber daya bersama

Pernyataan ini lock memperoleh kunci pengecualian bersama untuk objek tertentu, menjalankan blok pernyataan, lalu melepaskan kunci. Saat kunci sedang dipegang, utas yang memegang kunci dapat kembali memperoleh dan melepaskan kunci. Utas lain diblokir untuk memperoleh kunci dan menunggu hingga kunci dilepaskan. Pernyataan ini lock memastikan bahwa maksimal hanya satu utas yang mengeksekusi tubuhnya kapan saja.

Pernyataan mengambil lock formulir berikut:

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

Variabel x adalah ekspresi jenis System.Threading.Lock , atau jenis referensi. Ketika x diketahui pada waktu kompilasi untuk menjadi jenis System.Threading.Lock, justru setara dengan:

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

Objek yang dikembalikan oleh Lock.EnterScope() adalah ref struct yang menyertakan Dispose() metode . Pernyataan yang dihasilkan using memastikan cakupan dirilis bahkan jika pengecualian dilemparkan dengan isi lock pernyataan.

Jika tidak, lock pernyataan tersebut justru setara dengan:

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

Karena kode menggunakan try-finally pernyataan, kunci dilepaskan bahkan jika pengecualian dilemparkan dalam isi lock pernyataan.

Anda tidak dapat menggunakan await ekspresi dalam isi lock pernyataan.

Panduan

Dimulai dengan .NET 9 dan C# 13, kunci instans objek khusus dari jenis System.Threading.Lock untuk performa terbaik. Selain itu, pengkompilasi mengeluarkan peringatan jika objek yang diketahui Lock dilemparkan ke jenis lain dan dikunci. Jika menggunakan versi .NET dan C#yang lebih lama, kunci instans objek khusus yang tidak digunakan untuk tujuan lain. Hindari menggunakan instans objek kunci yang sama untuk sumber daya bersama yang berbeda, karena dapat mengakibatkan kebuntuan atau mengunci ketidakcocokan. Secara khusus, hindari menggunakan instans berikut sebagai objek kunci:

  • this, karena penelepon mungkin juga mengunci this.
  • Type instans, karena mungkin diperoleh oleh operator atau refleksi typeof .
  • instans string, termasuk literal string, karena mungkin diinternasi.

Tahan kunci untuk waktu sesingkat mungkin untuk mengurangi ketidakcocokan kunci.

Contoh

Contoh berikut mendefinisikan Account kelas yang menyinkronkan akses ke bidang privatnya balance dengan mengunci instans khusus balanceLock . Menggunakan instans yang sama untuk penguncian memastikan bahwa dua utas yang berbeda tidak dapat memperbarui balance bidang dengan memanggil Debit metode atau Credit secara bersamaan. Sampel menggunakan C# 13 dan objek baru Lock . Jika Anda menggunakan versi C# yang lebih lama atau pustaka .NET yang lebih lama, kunci instans 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));
            }
        }
    }
}

Spesifikasi bahasa C#

Untuk informasi selengkapnya, lihat Bagian pernyataan kunci dari spesifikasi bahasa C#.

Lihat juga