instruction lock : garantissez un accès exclusif à une ressource partagée.

L’instruction lock obtient le verrou d’exclusion mutuelle d’un objet donné, exécute un bloc d’instructions, puis libère le verrou. Tant qu’un verrou est maintenu, le thread qui contient le verrou peut à nouveau obtenir et libérer le verrou. Tout autre thread se voit bloquer l’obtention du verrou et attend que ce dernier soit libéré. L’instruction lock garantit qu’un thread unique dispose d’un accès exclusif à cet objet.

L’instruction lock se présente sous la forme

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

x est une expression de type référence. Elle équivaut précisément à

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

Dans la mesure où le code utilise un bloc try...finally, le verrou est libéré même si une exception est levée dans le corps d’une instruction lock.

Vous ne pouvez pas utiliser l’opérateur await dans le corps d’une instruction lock.

Consignes

Quand vous synchronisez l’accès des threads à une ressource partagée, verrouillez une instance d’objet dédiée (par exemple private readonly object balanceLock = new object();) ou toute autre instance peu susceptible d’être utilisée comme objet de verrouillage par des parties du code non associées. Évitez d’utiliser la même instance d’objet de verrouillage pour différentes ressources partagées, car cela peut entraîner une contention d’interblocage ou de verrouillage. En particulier, évitez d’utiliser les types suivants comme objets de verrouillage :

  • this, qui peut être utilisé en tant que verrou par les appelants.
  • Type instances, car ces objets peuvent être obtenus par l’opérateur typeof ou la réflexion.
  • instances de chaîne, y compris les littéraux de chaîne, car les littéraux de chaîne peuvent être internés.

Maintenez un verrou aussi court que possible pour réduire la contention de verrou.

Exemple

L’exemple suivant définit une classe Account, qui synchronise l’accès à son champ balance privé en verrouillant une instance balanceLock dédiée. L’utilisation de la même instance pour le verrouillage garantit que le balance champ ne peut pas être mis à jour simultanément par deux threads qui tentent d’appeler les Debit méthodes ou Credit simultanément.

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

spécification du langage C#

Pour plus d’informations, voir la section Instruction lock de la spécification du langage C#.

Voir aussi