Ключевое слово lock используется в языке C# для обозначения так называемой исключающей блокировки, используется при работе с потоками (thread). Это один из способов решения (имхо – самый верный) решения проблемы чтения-записи данных разными потоками.

Рассмотрим на примере кода небольшой программы (Листинг 1). Есть коллекция типа Int, содержащая 4 элемента. Есть 2 потока – один (th1) читает данные из коллекции, другой (th2) – добавляет новое значение в коллекцию.

Состояния потоков для Листинга 1:

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

 

Рис.1. Ошибка изменения коллекции

Рис.1. Ошибка изменения коллекции

 

Поток-писатель изменил коллекцию, которую в это же время читал поток-читатель, что и привело к ошибке.

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