lock, instrukcja (odwołanie w C#)
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 jest w formularzu
lock (x)
{
// Your code...
}
gdzie x jest wyrażeniem typu odwołania. Jest to dokładnie równoważne
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 metody try... wreszcie zablokować blokada jest zwalniana, nawet jeśli wyjątek jest zgłaszany w treści instrukcji lock .
Nie można użyć operatora await w treści instrukcji lock .
Wytyczne
Podczas synchronizowania dostępu wątku do zasobu udostępnionego blokada wystąpienia dedykowanego obiektu (na przykład ) lub innego wystąpienia, private readonly object balanceLock = new object();które jest mało prawdopodobne, aby było używane jako obiekt blokady przez niepowiązane części kodu. 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 obiektów jako obiektów blokady:
this, ponieważ może być używany przez wywołujących jako blokadę.- Type wystąpienia, jak te 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ę przez możliwie krótki czas, aby zmniejszyć rywalizację o blokadę.
Przykład
W poniższym przykładzie zdefiniowano klasę Account , która synchronizuje dostęp do swojego pola prywatnego balance przez blokowanie w dedykowanym balanceLock wystąpieniu. Użycie tego samego wystąpienia do blokowania zapewnia, że balance pole nie może być jednocześnie aktualizowane przez dwa wątki próbujące wywołać Debit metody lub Credit jednocześnie.
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));
}
}
}
}
specyfikacja języka C#
Aby uzyskać więcej informacji, zobacz sekcję instrukcja lockspecyfikacji języka C#.