Страницы

Поиск по вопросам

суббота, 30 ноября 2019 г.

Допустимо ли использовать yield return внутри блокировки?

#c_sharp #lock #yield


Есть два вопроса к коду, приведенному ниже:


Допустимо ли использовать yield return внутри блокировки?
Что произойдёт с блокировкой, когда мы будет крутить метод Get() в цикле foreach?

public IEnumerable Get()
{
  _lock.EnterReadLock();
  try
  {
    foreach (var item in _dictionary)
    {
      yield return new SomeObject(item.Key, item.Value);
    }
  }
  finally
  {
    _lock.ExitReadLock();
  }
}

private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();


    


Ответы

Ответ 1



Смотрите, это возможно на уровне языка — компилятор не имеет возможности проверить. Но делать так не рекомендуется. Общее правило такое: следует избегать вызова чужого кода под блокировкой. Допустим, вы нарушили правило и выполнили yield под блокировкой. Клиентский код может при этом сделать вызов в другой поток (если у нас нету многопоточности, то блокировки нам всё равно не нужны, правильно?). foreach (SomeObject o in Get()) { Dispatcher.Invoke(() => Process(o)); } Если в методе Process будет браться та же блокировка, у вас будет взаимная блокировка, и код зависнет: Process будет дожидаться отпускания блокировки, а блокирующий код — возвращения из yield (то есть, следующей итерации цикла).

Ответ 2



Ограниченно допустимо. Когда вы такую последовательность будете обходить в цикле foreach - то перед входом в цикл блокировка будет взята, а после выхода - освобождена. Также блокировка будет освобождена при любом прерывании цикла. Однако, использование IEnumerable не ограничивается простыми циклами. При использовании блокировок всегда важно небольшое время удержания блокировки - а тут вы его растягиваете на не зависящее от вас время! Так можно легко и до взаимоблокировки доиграться. Обычно лучше всего под блокировкой получить копию данных, и работать в дальнейшем уже с ней: public IEnumerable Get() { IEnumerable> d; _lock.EnterReadLock(); try { d = _dictionary.ToList(); } finally { _lock.ExitReadLock(); } foreach (var item in d) { yield return new SomeObject(item.Key, item.Value); } } Или даже вот так: public IEnumerable Get() { _lock.EnterReadLock(); try { return _dictionary.Select(item => new SomeObject(item.Key, item.Value)).ToList(); } finally { _lock.ExitReadLock(); } }

Комментариев нет:

Отправить комментарий