Эффективно записывать список файлов большого размера в файл на C#

#c# #.net #multithreading #file

#c# #.net #многопоточность #файл

Вопрос:

У меня есть то, что я считаю довольно распространенной проблемой, но мне не удалось найти хорошее решение самостоятельно или просматривая этот форум.

Проблема

Я написал инструмент для получения списка файлов папки с некоторой дополнительной информацией, такой как имя файла, путь к файлу, размер файла, хэш и т.д.

Самая большая проблема, с которой я сталкиваюсь, заключается в том, что некоторые папки содержат миллионы файлов (возможно, 50 миллионов в структуре).

Возможные решения

У меня есть два решения, но ни одно из них не идеально.

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

  2. Каждый раз, когда файл считывается, информация добавляется в некоторую форму коллекции, такую как ConcurrentBag. Это означает, что я могу многопоточно перечислять файлы и добавлять их в коллекцию. Как только перечисление будет выполнено, я могу записать всю коллекцию в файл с помощью File .WriteAllLines; однако добавление 50 миллионов записей в коллекцию приводит к нехватке памяти на большинстве компьютеров.

Другие варианты

Есть ли какой-нибудь способ добавить элементы в коллекцию, а затем записать их в файл, когда он дойдет до определенного количества записей в коллекции или что-то в этом роде?

Я изучил BlockingCollection, но это просто очень быстро заполнится, поскольку производитель будет многопоточным, но потребитель будет только однопоточным.

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

1. Вы можете выбрать свой вариант 2, но сбросить его в файл, как только количество записей превысит некоторый предопределенный порог, т.е. Записать в файл и периодически очищать свой параллельный пакет.

2. Примечание: диск (и все другие операции ввода-вывода), как правило, связаны с вводом-выводом, а не с процессором — выполнение таких многопоточных операций с одним источником не приведет к необходимости повышения производительности (и, возможно, замедления работы). Основная причина, по которой люди это делают, заключается в том, что написание такого кода более интересно, чем однопоточная операция — убедитесь, что вы знаете, что причина, по которой вы это делаете, соответствует вашим целям.

3. @AlexeiLevenkov, спасибо за это. Это приложение предназначено для использования на серверах хранения, поэтому ввод-вывод не должен быть такой же проблемой, как запуск его на диске одной машины.

4. Я согласен с @AlexeiLevenkov, я сомневаюсь, что это сценарий, в котором многопоточность действительно повысит производительность, а скорее замедлит ее. Но попробуйте провести несколько тестов.

5. Если вы его не используете, может быть полезен метод DirectoryInfo.EnumerateFiles . Вы не показали никакого кода, поэтому я должен был предложить это.

Ответ №1:

Создайте поток файлов, который является общим для всех потоков. Перед записью в этот файловый поток поток должен заблокировать его. FileStream имеет некоторый буфер (4096 байт, если я правильно помню), поэтому он фактически не записывает на диск каждый раз. Вы можете использовать BufferedStream для этого, если 4096 байт все еще недостаточно.

Ответ №2:

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

Если чтение является основной операцией по времени, очередь будет почти пустой все время, а общее время будет чуть больше времени чтения.

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

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