Share via


Lås-instruktionen – säkerställa exklusiv åtkomst till en delad resurs

-instruktionen lock hämtar låset för ömsesidig uteslutning för ett visst objekt, kör ett instruktionsblock och släpper sedan låset. Medan ett lås hålls, kan tråden som innehåller låset igen hämta och frigöra låset. Alla andra trådar blockeras från att hämta låset och väntar tills låset släpps. -instruktionen lock ser till att endast en tråd kör dess brödtext när som helst.

Instruktionen lock har följande formulär:

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

Variabeln x är ett uttryck av System.Threading.Lock typen eller en referenstyp. När x är känt vid kompileringstiden för att vara av typen System.Threading.Lockär det exakt detsamma som:

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

Objektet som returneras av Lock.EnterScope() är en ref struct som innehåller en Dispose() metod. Den genererade using instruktionen säkerställer att omfånget släpps även om ett undantag utlöses med instruktionens lock brödtext.

Annars motsvarar -instruktionen lock exakt följande:

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

Eftersom koden använder en try-finally -instruktion frigörs låset även om ett undantag utlöses i brödtexten i en lock -instruktion.

Du kan inte använda await uttrycket i brödtexten i en lock -instruktion.

Riktlinjer

Från och med .NET 9 och C# 13 låser du en dedikerad objektinstans av typen System.Threading.Lock för bästa prestanda. Kompilatorn utfärdar dessutom en varning om ett känt Lock objekt skickas till en annan typ och låses. Om du använder en äldre version av .NET och C# låser du på en dedikerad objektinstans som inte används för något annat ändamål. Undvik att använda samma låsobjektinstans för olika delade resurser, eftersom det kan leda till dödläge eller låskonkurrering. Undvik särskilt att använda följande instanser som låsobjekt:

  • this, eftersom anropare också kan låsa this.
  • Type instanser, eftersom de kan hämtas av typ av operator eller reflektion.
  • stränginstanser, inklusive strängliteraler, eftersom de kan vara internerade.

Håll ett lås så kort tid som möjligt för att minska låskonkurrationen.

Exempel

I följande exempel definieras en Account klass som synkroniserar åtkomsten till det privata balance fältet genom att låsa på en dedikerad balanceLock instans. Om du använder samma instans för låsning ser du till att två olika trådar inte kan uppdatera balance fältet genom att anropa Debit metoderna eller Credit samtidigt. Exemplet använder C# 13 och det nya Lock objektet. Om du använder en äldre version av C# eller ett äldre .NET-bibliotek låser du en instans av 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));
            }
        }
    }
}

Språkspecifikation för C#

Mer information finns i avsnittet låsuttryck i C#-språkspecifikationen.

Se även