CsvHelper - Чтение Потока Асинхронно
У меня есть служба, которая принимает входной поток, содержащий CSV-данные, которые должны быть массово вставлены в базу данных, и мое приложение использует async/await везде, где это возможно.
Процесс таков: проанализируйте поток с помощью CsvParser CsvHelper, добавьте каждую строку в DataTable, используйте SqlBulkCopy для копирования DataTable в базу данных.
Данные могут быть любого размера, поэтому я хотел бы избежать чтения всего этого в память за один раз-очевидно, что у меня будут все эти данные в DataTable конец в любом случае так бы по существу иметь 2 копии в памяти.
Я хотел бы сделать все это как можно асинхроннее, но CsvHelper не имеет асинхронных методов, поэтому я придумал следующий обходной путь:
using (var inputStreamReader = new StreamReader(inputStream))
{
while (!inputStreamReader.EndOfStream)
{
// Read line from the input stream
string line = await inputStreamReader.ReadLineAsync();
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var memoryStreamReader = new StreamReader(memoryStream))
using (var csvParser = new CsvParser(memoryStreamReader))
{
await streamWriter.WriteLineAsync(line);
await streamWriter.FlushAsync();
memoryStream.Position = 0;
// Loop through all the rows (should only be one as we only read a single line...)
while (true)
{
var row = csvParser.Read();
// No more rows to process
if (row == null)
{
break;
}
// Add row to DataTable
}
}
}
}
Есть ли какие-либо проблемы с этим решением? Это вообще необходимо? Я видел, что разработчики CsvHelper специально не добавили асинхронную функциональность (https://github.com/JoshClose/CsvHelper/issues/202 ) но я действительно не следую рассуждениям, стоящим за не делающий так.
EDIT: я только что понял, что это решение не будет работать для тех случаев, когда столбец содержит разрыв строки в любом случае : (думаю, мне просто придется скопировать весь входной поток в поток памяти или что-то еще
EDIT2: еще немного информации.
Это асинхронный метод в библиотеке,где я пытаюсь выполнить асинхронность полностью. Скорее всего, он будет потребляться контроллером MVC (если бы я просто хотел выгрузить его из потока пользовательского интерфейса, я бы просто поставил задачу.Запустить его). В основном метод будет ждать на внешних источниках, таких как database / DFS, и я хотел бы, чтобы поток был освобожден, пока он есть.
CsvParser.Read() будет блокировать, даже если блокирует чтение потока (например, если данные, которые я пытаюсь прочитать, находятся на сервере на другой стороне мира), тогда как если CsvHelper реализует асинхронный метод, который использует TextReader.ReadAsync (), то я не буду заблокирован в ожидании прибытия моих данных из Дубая. Насколько я могу скажите, Я не прошу асинхронную оболочку вокруг синхронного метода.
2 ответов:
Эрик Липперт объяснил полезность асинхронного ожидания, используя метафору приготовления пищи в ресторане. Согласно его объяснению, не стоит делать что-то асинхронно, если вашему потоку больше нечего делать.
Также имейте в виду, что пока ваш поток делает что-то, он не может делать что-то еще. Только если ваша нить чего-то ждет, она может делать и другие вещи. Одна из вещей, которую вы ждете в своем процессе, - это чтение файла. В то время как поток чтение файла он должен ждать несколько раз, чтобы части файла были прочитаны. Во время этого чтения он может делать и другие вещи, например, разбирать прочитанные CSV-данные и отправлять их в пункт назначения.
Синтаксический анализ данных-это не процесс, в котором поток должен ждать завершения какого-то другого процесса, как это происходит при чтении файла или отправке данных в базу данных. Вот почему не существует асинхронной версии процесса синтаксического анализа. Обычный async-await не поможет сохранить ваш поток занят, потому что во время процесса разбора нечего ждать, поэтому во время разбора у вашего потока не будет времени сделать что-то еще.
Конечно, вы можете преобразовать процесс синтаксического анализа в ожидаемую задачу, используя Task.Run (() = > ParseReadData(...)), и ждать, пока эта задача будет завершена, но по аналогии с рестораном Эрика Липперта это будет размораживание повара, чтобы сделать работу, в то время как вы сидите за стойкой, ничего не делая.
Однако, если ваш поток имеет что-то значимое делать, пока читаются CSV-данные, например, отвечать на ввод пользователя, тогда было бы полезно начать разбор в отдельной задаче.
Если ваш полный процесс чтения-разбора-обновления базы данных не нуждается во взаимодействии с пользователем, но вам нужно, чтобы ваш поток был свободен делать другие вещи во время выполнения процесса, рассмотрите возможность поместить полный процесс в отдельную задачу и запустить задачу, не дожидаясь ее. В этом случае вы используете только свой интерфейсный поток для запуска другой задачи, и ваш интерфейсный поток свободен делать другие вещи. Запуск этой новой задачи-относительно небольшая стоимость по сравнению с общим временем вашего процесса.
Еще раз: если вашему потоку больше нечего делать, пусть этот поток выполняет обработку, не запускайте для этого другие задачи.
Вот хорошая статья об использовании асинхронных оболочек для методов синхронизации и о том, почему CsvHelper этого не сделал. http://blogs.msdn.com/b/pfxteam/archive/2012/03/24/10287244.aspx
Если вы не хотите блокировать поток пользовательского интерфейса, запустите обработку в фоновом потоке.
CsvHelper извлекает буфер данных. Размер буфера-это параметр, который вы можете изменить, если хотите. Если ваш сервер находится на другом конце света, он буферизует некоторые данные, а затем считывает их. Больше, чем скорее всего, потребуется несколько чтений, прежде чем буфер будет использован.
CsvHelper также выдает записи, поэтому, если вы на самом деле не получаете строку, ничего не читается. Если Вы читаете только пару строк, то читается только та часть файла (фактически размер буфера).
Если вы беспокоитесь о памяти, есть несколько простых вариантов.
- буфер данных. Вы можете выполнять массовое копирование в 100 или 1000 строк за один раз вместо всего файла. Просто продолжайте делать это, пока файл не будет сделано.
- используйте поток файлов. Если вам нужно прочитать весь файл сразу по какой-то причине, используйте FileStream вместо этого и запишите все это на диск. Это будет медленнее, но вы не будете использовать кучу памяти.
Comments