Нужен ли lock в этом коде?

215
30 апреля 2018, 23:17

Подскажите пожалуйста нужен ли lock в этом коде, если я собираюсь использовать его в Parallel.ForEach? Пример кода с lock:

private IEnumerable<(Byte[] part_bytes, Int32 part_number)> GetPartsFile(FileStream file_stream)
    {
        file_stream.Seek(offset: 0, origin: SeekOrigin.Begin);
        for (Int32 index = 0, part_number = 0; index < file_stream.Length; index += DefaultCopyBufferSize, part_number++)
        {
            lock (this)
            {
                Byte[] bytes = new Byte[DefaultCopyBufferSize];
                Int32 readed_bytes = file_stream.Read(array: bytes, offset: index, count: DefaultCopyBufferSize);
                if (readed_bytes < DefaultCopyBufferSize)
                {
                    Byte[] end_bytes = new Byte[readed_bytes];
                    Buffer.BlockCopy(src: bytes, srcOffset: 0, dst: end_bytes, dstOffset: 0, count: readed_bytes);
                    yield return (end_bytes, part_number);
                }
                else
                {
                    yield return (bytes, part_number);
                }
            }
        }
    }

Пример использования в Parallel.ForEach:

Parallel.ForEach(
source: GetPartsFile(file_stream: zip_file),
body: tuple =>
{
    //тут ещё какие то действия
});
Answer 1

По поводу SynchronizationLockException. Ваш метод возвращает IEnumerable. Когда вы указываете этот метод в качестве источника данных для Parallel.ForEach(), это значит, что разные элементы последовательности могут быть запрошены и обработаны разными потоками.

Но тут в дело вступает yield return. При наличии такой конструкции итератор работает немного по-другому:

  1. Метод начинает выполняться, происходит первая итерация цикла.
  2. Метод возвращает первый элемент последовательности, при этом первая итерация цикла еще не закончена.
  3. При запросе следующего элемента завершается первая итерация, начинается вторая, возвращается второй элемент. Вторая итерация при этом тоже не закончена.
  4. И так по кругу.

Теперь если скомбинировать Parallel.ForEach(), yield return и lock, получим такой сценарий:

  1. Первый поток запрашивает первый элемент: выполняется первая итерация цикла, захватывается лок, возвращается первый элемент. Лок не освобождается.
  2. Второй поток запрашивает следующий элемент: завершается первая итерация, код пытается освободить лок и мы получаем исключение SynchronizationLockException, которое говорит о том, что поток попытался вызвать метод монитора, которым он не владеет. Потому что в монитор входил первый поток, а выйти из него пытается второй поток.
Answer 2

Именно для работы метода Parallel.ForEach оператор lock в этом коде не нужен.

Параметр source - источник - выполняется в одном потоке. А вот body - тело - может выполняться в нескольких потоках (а может и в одном).

Отмечу, что блокировака на this является опасной. Если кто-либо ещё возьмёт лок на этот же объект этого класса, то это может привести к дедлоку.

READ ALSO
Как имитировать некоторые действия

Как имитировать некоторые действия

Помогите имитировать клик, х2 клик, правый клик, CTRL+V в C#У меня программа голосовое управление, надо чтобы эти действия обезательно были

242
Ограничения универсального типа

Ограничения универсального типа

Был взят за основу код из ответа на SOНужно была доработка класса MessageHandlerAdapter чтобы в качестве базового типа была команда ICommand<IMessage>, но при...

190
Примеры сверточных нейронных сетей

Примеры сверточных нейронных сетей

Где можно найти полноценные примеры сверточных нейронных сетей для C# без применения сторонних библиотек?

197