Katghoti Ответов: 2

Считывание большого файла с диска по частям для отправки в API .Чистое ядро


I have a large number of files that I need to send to an external API for processing. My project is a .net core API that receives a POST to my controller when a file needs to be processed. The information passed into the controller will have the location of the file to download. I need to download this file (some of these files can be 5 gig), then take this file and split it into chunks and post these chunks to outside API for processing. I am having trouble reading the file in chunks. The file will be saved locally so I have access to it, but I am having problems reading the chunks and POSTing them. How do you read large files in chunks in .NET core?

Спасибо.

Что я уже пробовал:

Я пробовал использовать поток.Read и StreamReader, но я продолжаю сталкиваться с проблемами с фрагментированием данных. Например, не хватает памяти или создается фрагмент без данных.

Richard Deeming

Возможно, Вам стоит взглянуть на пакет System.IO.Pipelines, который был разработан для такого рода вещей?
Системы.ИО.Трубопроводов: высокая производительность ввода-вывода в .Сеть | .Чистый блог[^]

Katghoti

По состоянию на январь 2019 года официальной поддержки чтения больших файлов кусками с помощью этой библиотеки не существует, и она была создана для сетевых случаев использования. Начиная с версии 2.1, нет никакой поддержки для каких-либо точек пути файла.

Richard Deeming

Это позор.

Значит, Вы читаете файл с локального компьютера? Это должно быть довольно легко сделать с помощью Stream класс. Где ты застрял?

Katghoti

Я смотрю на класс Stream с помощью readblockasync. Я посмотрю, куда это приведет меня, и, возможно, вернусь для конкретных вопросов. Спасибо за помощь.

Katghoti

Кроме того, я должен упомянуть, что это двоичный файл (MP4), который я читаю. Вот тут-то я и застрял.

Richard MacCutchan

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

2 Ответов

Рейтинг:
13

lmoelleb

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

Если вам нужно отправить сообщение в API с помощью фрагментированного транспорта HTTP, просто откройте исходный файл в виде потока, а затем используйте StreamContent с HttpClient.

Если "chunked" относится к API, ожидающему, что данные будут загружены с помощью нескольких почтовых вызовов, каждый вызов передает подмножество данных, то есть несколько вариантов:

Если использование памяти (и производительность) не является большой проблемой, это создает разумный читаемый код:

1) Откройте файловый поток
2) Создайте BinaryReader для потока
3) считайте байты размера блока из BinaryReader.
4) опубликуйте данные в API
5) перейдите к шагу 3) если он не завершен.

Если использование памяти все еще является проблемой, убедитесь, что вы выделили однобайтовый массив с требуемым размером фрагмента и используете BinaryReader.Read(byte[] buffer, int index, int count) метод в шаге 3). Вы даже можете сделать это в самом потоке (таким образом, минуя BinaryReader), но тогда вам нужно понять этот поток.Read не всегда считывает ожидаемое количество байтов, что вынуждает вас писать дополнительный цикл. Вы также можете позволить BinaryReader сделать это за вас.

Если использование памяти/производительности вызывает беспокойство, то вы можете реализовать свой собственный "поток чанков". Это не очень сложно, но все же - всегда есть место для ошибок :). Может быть, разумный пакет nuget доступен, так что вам не нужно реализовывать его самостоятельно, я не знаю.

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

Реализовать его:

Сделайте так, чтобы класс назывался чем-то осмысленным (самое трудное-это иметь приличное имя). FileChunkStream или что-то еще должно сделать.

Добавьте конструктор, который принимает поток (который будет открываемым вами файловым потоком) и размер блока).

Добавьте поток базового класса

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

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

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

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


Рейтинг:
1

Katghoti

Оказывается, я слишком много думал об этом. Этот код вытащит файл размером 1 гигабайт примерно за 50 секунд:

static public class FileDownloadAsync
    {
        static public async Task DownloadFile(string filename)
        {
            //File name is 1GB.zip for testing
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            using (HttpClient client = new HttpClient())
            {
                string url = @"http://speedtest.tele2.net/" + filename;
                using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
                using (Stream readFrom = await response.Content.ReadAsStreamAsync())
                {
                    string tempFile = $"D:\\Test\\{filename}";
                    using (Stream writeTo = File.Open(tempFile, FileMode.Create))
                    {
                        await readFrom.CopyToAsync(writeTo);
                    }
                }
                stopwatch.Stop();
                Debug.Print(stopwatch.Elapsed.ToString());
            }
            

        }
    }