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åsathis
.- 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.