﻿# V3090\. Unsafe locking on an object\.

Анализатор обнаружил фрагмент кода с использованием небезопасной блокировки\.

Данная диагностика срабатывает на ряд случаев:

1. использование 'this' в качестве объекта блокировки;
1. использование в качестве объекта блокировки экземпляра класса 'Type', 'MemberInfo', 'ParameterInfo', 'String', 'Thread';
1. использование в качестве объекта блокировки публичного члена текущего класса;
1. использование в качестве объекта блокировки объекта, полученного в результате упаковки;
1. блокировка происходит по вновь создаваемым объектам\.

Первые три случая могут привести к возникновению взаимной блокировки, два последних \- к отсутствию желаемой синхронизации\. Общая суть первых трёх случаев состоит в том, что к объекту, используемому для блокировки, имеется общий доступ\. Такой объект может быть использован для блокировки в другом месте без ведома разработчика, использовавшего объект для блокировки в первый раз\. Это, в свою очередь, создаёт вероятность возникновения взаимоблокировки на один и тот же объект\.

Блокировка с использованием 'this' небезопасна, если класс не является приватным\. Тогда в другом месте программист может установить блокировку на этот же объект после создания его экземпляра\.

Аналогичная ситуация с публичными членами классов\. 

Для решения описанных выше ситуаций достаточно использовать, например, приватное поле класса в качестве объекта блокировки\.

Приведём пример небезопасного кода с использованием оператора 'lock' и 'this' в качестве объекта блокировки:

```cpp
class A
{
  void Foo()
  {
    lock(this)
    {
      // do smt
    }
  }
}
```

Для того, чтобы избежать возможных взаимных блокировок, в качестве объекта блокировки стоит использовать, например, приватное поле:

```cpp
class A
{
  private Object locker = new Object();
  void Foo()
  {
    lock(locker)
    {
      // do smt
    }
  }
}
```

С экземплярами класса 'Type', 'MemberInfo', 'ParameterInfo' ситуация несколько опаснее, так как здесь вероятность взаимной блокировки выше\. Используя оператор 'typeof' или метод 'GetType', метод 'GetMember' и т\.п\. для разных экземпляров одного типа, результат будет одинаков \- один и тот же экземпляр данного класса\.

Отдельно стоят объекты типов 'String' и 'Thread'\.

Доступ к объектам этих типов можно получить из любого места в программе, и даже из другого домена приложения \(Application Domain\), что ещё больше увеличивает опасность взаимной блокировки\. Решение \- не использовать в качестве объектов блокировки экземпляры этих типов\.

Рассмотрим пример возникновения взаимной блокировки\. Пусть имеется следующий код в каком\-нибудь приложении \(Sample\.exe\):

```cpp
static void Main(string[] args)
{
  var thread = new Thread(() => Process());
  thread.Start();
  thread.Join();
}
static void Process()
{
  String locker = "my locker";
  lock (locker)
  { 
    ....
  }
}
```

В другом приложении есть код следующего вида:

```cpp
String locker = "my locker";
lock (locker)
{
  AppDomain domain = AppDomain.CreateDomain("test");
  domain.ExecuteAssembly(@"C:\Sample.exe");
}
```

Результатом выполнения этого кода будет взаимная блокировка, возникшая в результате использования в качестве объекта блокировки экземпляра класса 'String'\. 

Мы создаём новый домен в рамках того же процесса и пытаемся выполнить в нём сборку, содержащуюся в другом файле \(Sample\.exe\)\. В итоге возникает ситуация, когда оба оператора 'lock' используют в качестве объекта блокировки одинаковый строковый литерал\. Для строковых литералов срабатывает механизм интернирования строк, за счёт которого в обоих случаях будут получены ссылки на один и тот же объект в памяти\. Как следствие \- оба оператора 'lock' выполняют блокировку по одному и тому же объекту, что и привело к взаимоблокировке\.

Эта ошибка могла бы проявить себя и при исполнении в рамках одного домена\.

Аналогичная ситуация с типом 'Thread', экземпляр которого можно легко получить, например, с помощью свойства 'Thread\.CurrentThread'\.

Решение \- не использовать в качестве объектов блокировки объекты типов 'Thread' и 'String'\. 

Блокировка с использованием объекта значимого типа приведёт к тому, что синхронизация потоков осуществляться не будет\. Стоит отметить, что конструкция 'lock' не позволяет выполнять блокировку на объектах значимого типа, но класс 'Monitor' с его методами 'Enter' и 'TryEnter' от этого не застрахован\.

Методы 'Enter' и 'TryEnter' ожидают в качестве параметра объект типа 'Object', поэтому, если в метод передаётся объект значимого типа, будет выполнена его 'упаковка'\. Это значит, что каждый раз для блокировки будет создаваться новый объект, следовательно \- блокировка будет устанавливаться \(и сниматься\) по этому новому объекту\. Результат \- отсутствие желаемой синхронизации\.

Рассмотрим пример ошибочного кода:

```cpp
sealed class A
{
  private Int32 m_locker = 10;

  void Foo()
  {
    Monitor.Enter(m_locker);
    // Do smt...
    Monitor.Exit(m_locker);
  }
}
```

Программист хотел установить блокировку по приватному полю 'm\_locker'\. На самом же деле блокировка будет устанавливаться \(и сниматься\) не по желаемому полю, а по вновь созданным объектам, полученным в результате 'упаковки'\. 

Для исправления данной ошибки достаточно изменить тип поля 'm\_locker' на допустимый ссылочный, например \- 'Object'\. Тогда пример корректного кода выглядел бы так:

```cpp
sealed class A
{
  private Object m_locker = new Object();

  void Foo()
  {
    Monitor.Enter(m_locker);
    // Do smt...
    Monitor.Exit(m_locker);
  }
}
```

Схожая ошибка проявится и при использовании конструкции 'lock', если выполняется упаковка объекта в результате приведения:

```cpp
Int32 val = 10;
lock ((Object)val)
{ .... }
```

В этом коде блокировка будет устанавливаться по объектам, полученным в результате упаковки\. Так как в результате упаковки будут создаваться новые объекты, синхронизации потоков не будет\.

Ошибочным является блокировка по вновь создаваемым объектам\. Пример подобного кода может выглядеть так:

```cpp
lock (new Object())
{ .... }
```

или так

```cpp
lock (obj = new Object())
{ .... }
```

Так как при выполнении этого кода каждый раз создаются новые объекты, блокировка также будет осуществляться по разным объектам, следовательно, потоки не будут синхронизироваться\.