Ключевое слово 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". При таком коде поток-писатель будет ждать права внесения изменения в коллекцию до тех пор, пока не отработает поток-читатель, т.е. в данном примере потоки выполнятся последовательно.