private void SomePrivateMethod()
{
// Использовать текущий объект как маркер потока.
lock(this)
{
// Весь код внутри этого блока является безопасным к потокам.
}
}
Тем не менее, если блокируется область кода внутри открытого члена, то безопаснее (да и рекомендуется) объявить закрытую переменную-член типа
object
для применения в качестве маркера блокировки:
public class Printer
{
// Маркер блокировки.
private object threadLock = new object();
public void PrintNumbers()
{
// Использовать маркер блокировки.
lock (threadLock)
{
...
}
}
}
В любом случае, если взглянуть на метод
PrintNumbers()
, то можно заметить, что разделяемым ресурсом, за доступ к которому соперничают потоки, является окно консоли. Поместите весь код взаимодействия с типом
Console
внутрь области
lock
, как показано ниже:
public void PrintNumbers()
{
// Использовать в качестве маркера блокировки закрытый член object.
lock (threadLock)
{
// Вывести информацию о потоке.
Console.WriteLine("-> {0} is executing PrintNumbers()",
Thread.CurrentThread.Name);
// Вывести числа.
Console.Write("Your numbers: ");
for (int i = 0; i < 10; i++)
{
Random r = new Random();
Thread.Sleep(1000 * r.Next(5));
Console.Write("{0}, ", i);
}
Console.WriteLine();
}
}
В итоге вы построили метод, который позволит текущему потоку завершить свою задачу. Как только поток входит в область
lock
, маркер блокировки (в данном случае ссылка на текущий объект) становится недоступным другим потокам до тех пор, пока блокировка не будет освобождена после выхода из области
lock
. Таким образом, если поток
А
получил маркер блокировки, то другие потоки не смогут войти
ни в одну из областей, которые используют тот же самый маркер, до тех пор, пока поток
А
не освободит его.
На заметку! Если необходимо блокировать код в статическом методе, тогда следует просто объявить закрытую статическую переменную-член типа
object
, которая и будет служить маркером блокировки.
Запустив приложение, вы заметите, что каждый поток получил возможность выполнить свою работу до конца:
*****Synchronizing Threads *****
-> Worker thread #0 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #1 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #3 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #2 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #4 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #5 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #7 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #6 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #8 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #9 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
Синхронизация с использованием типа System.Threading.Monitor
Оператор
lock
языка C# на самом деле представляет собой сокращение для работы с классом
System.Threading.Monitor
. При обработке компилятором C# область
lock
преобразуется в следующую конструкцию (в чем легко убедиться с помощью утилиты
ldasm.exe
):
public void PrintNumbers()
{
Monitor.Enter(threadLock);
try
{
// Вывести информацию о потоке.
Console.WriteLine("-> {0} is executing PrintNumbers()",
Thread.CurrentThread.Name);