Bagikan melalui


Pernyataan kunci - memastikan akses eksklusif ke sumber daya bersama

Pernyataan lock memperoleh kunci pengecualian bersama untuk objek tertentu, menjalankan blok pernyataan, lalu melepaskan kunci. Saat kunci ditahan, utas yang menahan kunci dapat kembali memperoleh dan melepas kunci. Setiap utas lain diblokir dari memperoleh kunci dan menunggu sampai kunci dilepaskan. Pernyataan ini lock memastikan bahwa maksimal hanya satu utas yang menjalankan isinya 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 perselisihan kunci. 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 perselisihan kunci.

Contoh

Contoh berikut mendefinisikan kelas Account 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 lock dari spesifikasi bahasa C#.

Lihat juga