Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
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#.