Ключевое слово lock используется в языке C# для обозначения так называемой исключающей блокировки, используется при работе с потоками (thread). Это один из способов решения (имхо – самый верный) решения проблемы чтения-записи данных разными потоками.
Рассмотрим на примере кода небольшой программы (Листинг 1). Есть коллекция типа Int, содержащая 4 элемента. Есть 2 потока – один (th1) читает данные из коллекции, другой (th2) – добавляет новое значение в коллекцию.
Состояния потоков для Листинга 1:
- Потоки в состоянии покоя
- Поток-читатель запускается с задержкой в 1000 мс, на каждой итерации цикла также происходит задержка на 1000 мс.
- Поток-писатель запускается с задержкой 1200мс.
Листинг 1
List<int> elements = new List<int>();
elements.Add(5);
elements.Add(10);
elements.Add(15);
elements.Add(20);
Thread th1 = new Thread(
() =>
{
Thread.Sleep(1000);
foreach (int item in elements)
{
Console.WriteLine("Item {0}", item.ToString());
Thread.Sleep(1000);
}
}
);
Thread th2 = new Thread(
() =>
{
Thread.Sleep(1200);
elements.Add(25);
Console.WriteLine("Добавлена запись в коллекцию");
}
);
th2.Start();
th1.Start();
Выполнение данной программы начнется с вывода надписей "Item 5" и "Добавлена запись в коллекцию, но следом будет сообщение об ошибке: "Необработанное исключение: System.InvalidOperationException: Коллекция была изменена; невозможно выполнить операцию перечисления.".
Поток-писатель изменил коллекцию, которую в это же время читал поток-читатель, что и привело к ошибке.
Для борьбы с такими ошибками нужно либо очень детально настраивать время выполнения потоков (threads), что, имхо, очень непросто, либо использовать так называемые "исключающие блокировки".
С оператором lock указывается параметр, на который распространяется область действия блокировки. Модифицируем код Листинга 1 так, чтобы на работу с коллекциями были наложены блокировки (Листинг 2)
Листинг 2
List<int> elements = new List<int>();
elements.Add(5);
elements.Add(10);
elements.Add(15);
elements.Add(20);
Thread th1 = new Thread(
() =>
{
Thread.Sleep(1000);
lock (elements)
{
foreach (int item in elements)
{
Console.WriteLine("Item {0}", item.ToString());
Thread.Sleep(1000);
}
}
}
);
Thread th2 = new Thread(
() =>
{
Thread.Sleep(1200);
lock (elements)
{
elements.Add(25);
Console.WriteLine("Добавлена запись в коллекцию");
}
}
);
В листинге 2 добавлены 2 исключающие блокировки для коллекции "elements". При таком коде поток-писатель будет ждать права внесения изменения в коллекцию до тех пор, пока не отработает поток-читатель, т.е. в данном примере потоки выполнятся последовательно.


