Это мой код для асинхронного чтения файла csv с использованием функции ReadLineAsync() из класса StreamReader , но он читает только первую строку файла csv
private async Task ReadAndSendJointDataFromCSVFileAsync(CancellationToken cancellationToken) {
Stopwatch sw = new Stopwatch();
sw.Start();
string filePath = @ "/home/adwait/azure-iot-sdk-csharp/iothub/device/samples/solutions/PnpDeviceSamples/Robot/Data/Robots_data.csv";
using(StreamReader oStreamReader = new StreamReader(File.OpenRead(filePath))) {
string sFileLine = await oStreamReader.ReadLineAsync();
string[] jointDataArray = sFileLine.Split(',');
// Assuming the joint data is processed in parallel
var tasks = new List < Task > ();
// Process joint pose
tasks.Add(Task.Run(async () => {
var jointPose = jointDataArray.Take(7).Select(Convert.ToSingle).ToArray();
var jointPoseJson = JsonSerializer.Serialize(jointPose);
await SendTelemetryAsync("JointPose", jointPoseJson, cancellationToken);
}));
// Process joint velocity
tasks.Add(Task.Run(async () => {
var jointVelocity = jointDataArray.Skip(7).Take(7).Select(Convert.ToSingle).ToArray();
var jointVelocityJson = JsonSerializer.Serialize(jointVelocity);
await SendTelemetryAsync("JointVelocity", jointVelocityJson, cancellationToken);
}));
// Process joint acceleration
tasks.Add(Task.Run(async () => {
var jointAcceleration = jointDataArray.Skip(14).Take(7).Select(Convert.ToSingle).ToArray();
var jointAccelerationJson = JsonSerializer.Serialize(jointAcceleration);
await SendTelemetryAsync("JointAcceleration", jointAccelerationJson, cancellationToken);
}));
// Process external wrench
tasks.Add(Task.Run(async () => {
var externalWrench = jointDataArray.Skip(21).Take(6).Select(Convert.ToSingle).ToArray();
var externalWrenchJson = JsonSerializer.Serialize(externalWrench);
await SendTelemetryAsync("ExternalWrench", externalWrenchJson, cancellationToken);
}));
await Task.WhenAll(tasks);
}
sw.Stop();
_logger.LogDebug(String.Format("Elapsed = {0}", sw.Elapsed));
}
По сути, файл CSV имеет 10128 строк. Я хочу прочитать последнюю строку, которая добавляется в файл csv.
Как мне это сделать?
Использование File.ReadLine(filePath) вызывает это исключение
Необработанное исключение. System.IO.PathTooLongException: путь '/home/adwait/azure-iot-sdk-csharp/iothub/device/samples/solutions/PnpDeviceSamples/Robot/-2.27625e-06,-0.78542,-3.79241e-06,-2.35622,5.66111e-06,3.14159 ,0.785408,0.00173646,-0.0015847,0.000962475,-0.00044469,-0.000247682,-0.000270337,0.000704195,0.000477503,0.000466693,-6. 50664e-05,0.00112044,-2.47425e-06,0.000445592,-0.000685786,1.21642,-0.853085,- 0.586162,-0.357496,-0.688677,0.230229' слишком длинный или компонент указанного пути слишком длинный.
private async Task ReadAndSendJointDataFromCSVFileAsync(CancellationToken cancellationToken) {
Stopwatch sw = new Stopwatch();
sw.Start();
string filePath = @ "/home/adwait/azure-iot-sdk-csharp/iothub/device/samples/solutions/PnpDeviceSamples/Robot/Data/Robots_data.csv";
using(StreamReader oStreamReader = new StreamReader(File.ReadLines(filePath).Last())) {
string sFileLine = await oStreamReader.ReadLineAsync();
string[] jointDataArray = sFileLine.Split(',');
// Assuming the joint data is processed in parallel
var tasks = new List < Task > ();
// Process joint pose
tasks.Add(Task.Run(async () => {
var jointPose = jointDataArray.Take(7).Select(Convert.ToSingle).ToArray();
var jointPoseJson = JsonSerializer.Serialize(jointPose);
await SendTelemetryAsync("JointPose", jointPoseJson, cancellationToken);
}));
// Process joint velocity
tasks.Add(Task.Run(async () => {
var jointVelocity = jointDataArray.Skip(7).Take(7).Select(Convert.ToSingle).ToArray();
var jointVelocityJson = JsonSerializer.Serialize(jointVelocity);
await SendTelemetryAsync("JointVelocity", jointVelocityJson, cancellationToken);
}));
// Process joint acceleration
tasks.Add(Task.Run(async () => {
var jointAcceleration = jointDataArray.Skip(14).Take(7).Select(Convert.ToSingle).ToArray();
var jointAccelerationJson = JsonSerializer.Serialize(jointAcceleration);
await SendTelemetryAsync("JointAcceleration", jointAccelerationJson, cancellationToken);
}));
// Process external wrench
tasks.Add(Task.Run(async () => {
var externalWrench = jointDataArray.Skip(21).Take(6).Select(Convert.ToSingle).ToArray();
var externalWrenchJson = JsonSerializer.Serialize(externalWrench);
await SendTelemetryAsync("ExternalWrench", externalWrenchJson, cancellationToken);
}));
await Task.WhenAll(tasks);
}
sw.Stop();
_logger.LogDebug(String.Format("Elapsed = {0}", sw.Elapsed));
}
Или Task.Run(() => File.ReadLines(filePath).Last());?
@Enigmativity, спасибо за ответ. Использование File.ReadLines() вызывает это исключение - обновлен вопрос выше.
Ну давай же. Вы действительно читали это сообщение об ошибке? Это не совсем имя вашего файла, не так ли? Похоже, вы добавили одну из строк из файла в путь к папке. Возможно, покажите новый код, который вы использовали, а не только сообщение об ошибке, чтобы мы могли увидеть, что вы сделали не так. Конечно, вы должны были увидеть, что имя файла неверное, и исправить его самостоятельно путем отладки.
@jmcilhinney, Да, это сообщение об ошибке, которое я прочитал. Я ничего не добавлял. Пожалуйста, найдите обновленный код выше
@Astroboy — Код показывает, что имя файла должно быть "/home/adwait/azure-iot-sdk-csharp/iothub/device/samples/solutions/PnpDeviceSamples/Robot/Data/Robots_data.csv", и оно сильно отличается от пути в исключении. Пожалуйста, покажите нам настоящий код, который вы используете.
@Enigmativity, вот ссылка на полный код. Извини! Я не смог вставить это в раздел выше
if (File.Exists(filePath)) строка sFileLine = File.ReadAllLines(filePath).LastOrDefault(); (если код может достичь файла)
исключение, которое вы показываете, не имеет никакого отношения к чтению какой-либо части файла. Просто вы передаете неверное имя файла. Не знаю, откуда это взялось, но исправьте свой код, генерирующий имя файла...
new StreamReader(File.ReadLInes(filePath).Last())) читает последнюю строку из файла и интерпретирует ее содержимое как имя файла для конструктора StreamReader.
Если вы вызываете File.ReadLines, а вам следует это сделать, вы НЕ используете StreamReader. Подумайте о том, что вы пишете. Как вы думаете, что делает File.ReadLines? Он читает строки файла. Как вы думаете, что делает Ласт? Он получает последний элемент последовательности; в данном случае последняя строка файла. Зачем тогда передавать последнюю строку в StreamReader?
@jmcilhinney, точная передача последней строки в программу чтения потока не имеет смысла, потому что она уже читается функцией ReadLines(), которая теперь указывает на последнюю строку в CSV.
И вы ошиблись в коде, который я разместил в комментариях. File.ReadLInes(filePath).Last() неправильно. Вам следует копировать и вставлять, а не перепечатывать. Вы также должны публиковать только тот код, который вы действительно запустили. В противном случае вы просите нас исправить основные синтаксические ошибки.





Медленный (но надежный) метод — прочитать все строки файла и вернуть последнюю. Что-то вроде:
static string? GetLastLine(string filename)
{
using StreamReader reader = new(filename);
string? last = null;
while (reader.ReadLine() is string line)
last = line;
return last;
}
Конечно, при этом без уважительной причины выделяется много строк, и сборщику мусора вскоре после этого придется избавиться от них всех. Не идеально, но так оно и есть. (Версия async достаточно проста в изготовлении, но она занимает несколько мс на МБ. На ваше усмотрение.)
Чтобы ускорить работу, вы можете попробовать просто прочитать последнюю часть файла — достаточную, чтобы получить пару строк — и обработать ее. Для этого вам нужно знать максимальную длину строки, чтобы гарантировать, что вы получите хотя бы одну полную строку. Предполагая, что вы работаете с данными ASCII или UTF8, вы, вероятно, можете сделать что-то вроде этого:
static string? GetLastLine(string filename, int maxLineLength)
{
using Stream stream = File.OpenRead(filename);
stream.Position = stream.Length - maxLineLength * 3 / 2;
using StreamReader reader = new(stream);
string? last = null;
while (reader.ReadLine() is string line)
last = line;
return last;
}
(Это должно работать за достаточно постоянное время, независимо от размера файла, поскольку оно всегда будет обрабатывать один и тот же объем данных — около 100 микросекунд на моей машине для файла размером 1 МБ с использованием maxLineLength = 150. Опять же, преобразование в версию async просто.)
И, наконец, если вы ожидаете, что строки будут добавляться в файл с течением времени, и вы хотите просто прочитать новые строки из файла, отслеживайте размер файла и возобновляйте чтение оттуда, когда размер файла изменится.
Как указано в комментариях, вышеизложенное не особенно полезно, если вы хотите читать новые строки, поступающие из-за пределов вашей программы. Он не только всегда возвращает последнюю строку файла, независимо от изменений, но и если добавлено несколько строк, вы получите только последнюю. Почти наверняка это не то, что вы хотите.
Вероятно, вам нужен способ читать новые строки по мере их поступления и не пропускать новые строки. Это означает отслеживание изменений длины файла и возобновление чтения по одной строке за раз. Вы не хотите блокировать файл слишком долго, и поскольку StreamReader буферизируется впереди, вы не можете рассчитывать на то, что stream.Position выровняется с концом строки, которую вы только что прочитали.
Решение состоит в том, чтобы каждый раз читать все возможные строки и сохранять их для последующего чтения. Мы можем помещать новые строки в Queue<> и отслеживать размер файла, чтобы определить, когда есть что прочитать. Что-то вроде этого:
sealed class LineReader
{
// File to read from.
private readonly string _filename;
// Queue for when multiple new lines are added between reads.
private readonly Queue<string> _queue = new();
// Length of file after last read.
private long _lastPosition;
// True if there's something to read.
public bool LinesAvailable
{
get
{
if (_queue.Count > 0)
return true;
FileInfo fi = new(_filename);
return fi.Exists && fi.Length != _lastPosition;
}
}
public LineReader(string filename, bool readExistingLines = false)
{
_filename = filename;
FileInfo fi = new(_filename);
_lastPosition = readExistingLines || !fi.Exists ? 0 : fi.Length;
}
// Returns when a line is read or token is cancelled.
public async Task<string?> WaitNextLineAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
if (_queue.TryDequeue(out string? queued))
return queued;
if (await GetNextLineAsync() is string line)
return line;
try
{
await Task.Delay(20, token);
}
catch (TaskCanceledException)
{
break;
}
}
return null;
}
// Read next line, or `null` if none available.
public async Task<string?> GetNextLineAsync()
{
if (_queue.TryDequeue(out string? line))
return line;
// Check if file length has changed.
FileInfo fi = new(_filename);
if (!fi.Exists || fi.Length == _lastPosition)
return null;
// Open the stream. May fail if file is locked.
Stream stream;
try
{
stream = fi.OpenRead();
}
catch
{
return null;
}
using (stream)
{
// If file is smaller assume it was cleared, read all lines.
// Otherwise, go to our last position.
if (stream.Length >= _lastPosition)
stream.Position = _lastPosition;
using StreamReader reader = new(stream);
// Queue up new lines
while (await reader.ReadLineAsync() is string next)
{
// Ignore empty lines.
if (!string.IsNullOrEmpty(next))
_queue.Enqueue(next);
}
}
// Update state and return the last read line if not empty.
_lastPosition = stream.Length;
_queue.TryDequeue(out line);
return line;
}
}
GetNextLineAsync() пытается получить следующую доступную строку, WaitNextLineAsync(...) вращается асинхронно, пока строка не будет прочитана. Отмена завершается вместо броска, что удобно, если вы хотите предоставить тайм-аут с помощью токена CancelAfter:
async Task ProcessNextLineAsync(LineReader reader, CancellationToken token)
{
CancellationTokenSource source = CancellationTokenSource.CreateLinkedTokenSource(token);
source.CancelAfter(TimeSpan.FromSeconds(1));
if (await LineReader.WaitNextLineAsync(source.Token) is string line)
{
// do something with the line
}
}
(Да, я мог бы сделать гораздо более простую версию. Хотя вы просили async.)
ПРИМЕЧАНИЕ. Это не является потокобезопасным.
Спасибо за подробное объяснение. Я использую CSV для хранения данных в реальном времени. Итак, да, строки постоянно добавляются в CSV-файл. Я думаю, что этот подход может сработать.
Сохранение последней позиции чтения, быстрая перемотка назад и последующее чтение только того, что нового, — это гораздо лучший подход, чем повторное чтение всех данных. Что, если с момента последнего анализа было добавлено более одной строки?
@Charlieface Вы должны сохранить предыдущую строку, так как последнее чтение — null.
@Sinatr Второй вариант переходит прямо примерно к последней строке.
@Charlieface Последний ReadLine() сбрасывает переменную line на null. Вот почему вам необходимо сохранить предыдущее значение в цикле.
@Sinatr Да, если добавить несколько строк, будет прочитана только последняя. В конце концов, это был вопрос... и единственный вариант тривиального метода «прочитать последнюю строку файла». Хуже того, это всегда будет возвращать последнюю строку, независимо от изменений или нет. Добавлю нетривиальное решение, которое гораздо полезнее.
Ах, ты прав
@Sinatr Лучше?
Вы подумали
File.ReadLines(filePath).Last()?