Istruzione lock (Riferimenti per C#)

L'istruzione lock acquisisce il blocco a esclusione reciproca per un oggetto specificato, esegue un blocco di istruzioni e quindi rilascia il blocco. Mentre è attivo un blocco, il thread che contiene il blocco può ancora acquisire e rilasciare il blocco. Gli altri thread non possono acquisire il blocco e devono attendere finché il blocco non viene rilasciato.

L'istruzione lock ha il formato

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

in cui x è un'espressione di un tipo riferimento. È esattamente equivalente a

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

Poiché il codice usa un blocco try... finally, il blocco viene rilasciato anche se viene generata un'eccezione nel corpo di un'istruzione lock.

Non è possibile usare l'operatore await nel corpo di un'istruzione lock.

Indicazioni

Quando si sincronizza l'accesso dei thread a una risorsa condivisa, applicare il blocco a un'istanza dell'oggetto dedicata (ad esempio private readonly object balanceLock = new object();) o a un'altra istanza che ha poche probabilità di essere usata come oggetto di blocco da parti del codice non correlate. Evitare di usare la stessa istanza di oggetto di blocco per diverse risorse condivise. Questo può originare problemi di deadlock o conflitti di blocco. In particolare, evitare di usare gli elementi seguenti come oggetti di blocco:

  • this, perché potrebbe essere usato dai chiamanti come blocco.
  • Istanze Type, in quanto possono essere ottenute dall'operatore typeof o dalla reflection.
  • Istanze stringa, tra cui i valori letterali stringa, in quanto potrebbero essere centralizzate.

Mantenere un blocco il più breve possibile per ridurre i problemi di blocco.

Esempio

L'esempio seguente definisce una classe Account che sincronizza l'accesso al proprio campo balance privato applicando il blocco su un'istanza balanceLock dedicata. L'uso della stessa istanza per il blocco garantisce che il campo balance non possa essere aggiornato contemporaneamente da due thread che provano a chiamare i metodi Debit o Credit allo stesso tempo.

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

Specifiche del linguaggio C#

Per altre informazioni, vedere la sezione Istruzione lock della specifica del linguaggio C#.

Vedi anche