Instrukcja lock — zapewnianie wyłącznego dostępu do udostępnionego zasobu
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 następującą formę:
lock (x)
{
// Your code...
}
Zmienna x
jest wyrażeniem System.Threading.Lock typu lub typem odwołania. Gdy x
w czasie kompilacji jest znany jako typ System.Threading.Lock, jest dokładnie odpowiednikiem:
using (x.EnterScope())
{
// Your code...
}
Obiekt zwracany przez Lock.EnterScope() element jest obiektem ref struct
zawierającym metodę Dispose()
. Wygenerowana using
instrukcja gwarantuje, że zakres zostanie zwolniony, nawet jeśli zostanie zgłoszony wyjątek z treścią lock
instrukcji .
lock
W przeciwnym razie instrukcja jest dokładnie równoważna:
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
Począwszy od platform .NET 9 i C# 13, zablokuj dedykowane wystąpienie System.Threading.Lock obiektu typu, aby uzyskać najlepszą wydajność. Ponadto kompilator wystawia ostrzeżenie, jeśli znany Lock
obiekt jest rzutowany na inny typ i zablokowany. Jeśli używasz starszej wersji platformy .NET i języka C#, zablokuj wystąpienie dedykowanego obiektu, które nie jest używane w innym celu. 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ż osoby wywołujące mogą również zablokowaćthis
funkcję .- 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 dwa różne wątki nie mogą zaktualizować balance
pola przez wywołanie Debit
metod lub Credit
jednocześnie. W przykładzie użyto języka C# 13 i nowego Lock
obiektu. Jeśli używasz starszej wersji języka C# lub starszej biblioteki .NET, zablokuj wystąpienie programu 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));
}
}
}
}
specyfikacja języka C#
Aby uzyskać więcej informacji, zobacz sekcję instrukcji lock specyfikacji języka C#.