lock — zapewnianie wyłącznego dostępu do zasobu udostępnionego

Instrukcja lock uzyskuje blokadę wzajemnego wykluczania dla danego obiektu, wykonuje blok instrukcji, a następnie zwalnia blokadę. Gdy blokada jest utrzymywana, wątek, który przechowuje blokadę, może ponownie uzyskać i zwolnić blokadę. Każdy inny wątek nie może nabyć blokady i czeka na zwolnienie blokady. Instrukcja lock zapewnia, że w dowolnym momencie tylko jeden wątek wykonuje treść.

Instrukcja lock ma postać

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

gdzie x jest wyrażeniem typu odwołania. Jest to dokładnie równoważne

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

Ponieważ kod używa try-finally instrukcji, blokada jest zwalniana, nawet jeśli wyjątek jest zgłaszany w treści instrukcji lock .

Nie można użyć await wyrażenia w treści instrukcji lock .

Wytyczne

Podczas synchronizowania dostępu wątku do zasobu udostępnionego zablokuj wystąpienie dedykowanego obiektu (na przykład private readonly object balanceLock = new object();) lub inne wystąpienie, które jest mało prawdopodobne, aby było używane jako obiekt blokady przez niepowiązane części kodu. Unikaj używania tego samego wystąpienia obiektu blokady dla różnych zasobów udostępnionych, ponieważ może to spowodować zakleszczenie lub zablokowanie rywalizacji. W szczególności należy unikać używania następujących wystąpień jako obiektów blokady:

  • this, ponieważ może być używany przez osoby wywołujące jako blokadę.
  • Type wystąpienia, ponieważ mogą być uzyskiwane przez operator typeof lub odbicie.
  • wystąpienia ciągów, w tym literały ciągów, ponieważ mogą być internowane.

Przytrzymaj blokadę tak szybko, jak to możliwe, aby zmniejszyć rywalizację o blokadę.

Przykład

W poniższym przykładzie zdefiniowano klasę Account , która synchronizuje dostęp z polem prywatnym balance przez zablokowanie wystąpienia dedykowanego balanceLock . Użycie tego samego wystąpienia do blokowania gwarantuje, że balance pole nie może być aktualizowane jednocześnie przez dwa wątki próbujące wywołać Debit metody lub Credit jednocześnie.

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));
            }
        }
    }
}

specyfikacja języka C#

Aby uzyskać więcej informacji, zobacz sekcję instrukcji lock specyfikacji języka C#.

Zobacz też