Обеспечивают ли блокировки потокобезопасность элементов списка?

Насколько я понимаю, их нет. Пожалуйста подтвердите.

Предположим, у меня есть объект Processor, который содержит List‹T›, где T — ссылочный тип.

Поток пользовательского интерфейса создает экземпляр объекта Processor и периодически обращается к нему для получения List‹T›.

Объект-процессор также запускает задачу, имеющую ссылку на List‹T›, когда он создается.

Блокировка используется для резервирования доступа к List‹T› в геттере и в задаче, чтобы гарантировать, что они имеют эксклюзивный доступ к List‹T›.

public class Processor
{
  private List<T> _list = new List<T>();
  private Object _listLock = new Object();

  public Processor()
  {
    // populate _list

    Task t = new Task(Process);

    t.Start();
  }

  public List<T> GetList()
  {
    lock(_listLock)
    {
      return _list;
    }
  }

  private void Process()
  {
    while(!doneProcessing)
    {
      lock(_listLock)
      {
        // access and modify _list items
      }

      Thread.Sleep(...);
    }
  }
}

Но даже если List‹T› заблокирован в геттере и он вернул ссылку на список без проблем, задача, запущенная процессором, по-прежнему изменяет элементы списка ссылочного типа, когда захватывает блокировку.

Элементы списка по-прежнему могут быть изменены задачей процессора, и доступ к ним в потоке пользовательского интерфейса не будет потокобезопасным.

Если я прав, очевидное решение состоит в том, чтобы геттер возвращал новый список, заполненный глубокими копиями элементов списка.

public List<T> GetList()
{
  lock(_listLock)
  {
    return _list.Select(t => t.Clone()).ToList();
  }
}

Что еще можно сделать?


person nv dev    schedule 24.11.2016    source источник
comment
Не ответ, а просто вызов ToList() создает копию списка.   -  person stuartd    schedule 24.11.2016
comment
Вы можете предоставить только те методы из List<T>, которые вы используете, а не весь список. Таким образом, вы можете использовать блокировку доступа и модификации элементов.   -  person Fredrik Lundvall    schedule 24.11.2016
comment
Вам ясно, что блокировка списка, подобного этому, не обеспечивает безопасности при изменении элементов списка?   -  person Enigmativity    schedule 24.11.2016


Ответы (2)


Блокировки, используемые так, как у вас, не обеспечат потокобезопасность.

Если вы пытаетесь обеспечить безопасность потоков, рассмотрите возможность использования одной из потокобезопасных коллекций. доступен в .NET CLR.

Вы заметите, что нет потокобезопасного IList. И вот почему. Само понятие безопасного списка потоков не имеет большого смысла. Но с небольшими изменениями в дизайне вы можете легко использовать что-то вроде ConcurrentDictionary. или ConcurrentBag.

person John Wu    schedule 24.11.2016

Ваш GetList() не делает того, что вы думаете:

public List<T> GetList()
  {
    lock(_listLock)
    {
      return _list;
    }
  }

Поскольку GetList() просто возвращает ссылку на _list, функция lock() ничего не делает, кроме предотвращения одновременного получения двумя потоками ссылки на _list, что в любом случае не является проблемой.

Проблема заключается в том, что вы передаете ссылку на объект списка обратно в поток пользовательского интерфейса, и элементы в списке, на которые указывает ссылка, могут измениться в любое время, событие, когда поток пользовательского интерфейса перебирает элементы, что не хороший.

Вы можете предоставить объект блокировки потоку пользовательского интерфейса, но это означает, что ваш пользовательский интерфейс должен будет блокироваться, пока список обновляется, что обычно нежелательно (блокировка потока пользовательского интерфейса ухудшает взаимодействие с пользователем).

В зависимости от того, что ваш пользовательский интерфейс делает со списком, может быть лучше сделать неизменяемый снимок вашего списка и вернуть его в пользовательский интерфейс.

Если вы хотите, чтобы пользовательский интерфейс соответствовал изменениям базовых данных списка, то подход, который вы выберете, действительно зависит от технологии пользовательского интерфейса.

person saille    schedule 24.11.2016