#c_sharp #lock #yield
Есть два вопроса к коду, приведенному ниже: Допустимо ли использовать yield return внутри блокировки? Что произойдёт с блокировкой, когда мы будет крутить метод Get() в цикле foreach? public IEnumerableGet() { _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 IEnumerableGet() { 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(); } }
Комментариев нет:
Отправить комментарий