Как прочитать последнюю строку из файла csv с помощью метода ReadLineAsync?

Это мой код для асинхронного чтения файла 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));
}

Вы подумали File.ReadLines(filePath).Last()?

Enigmativity 28.03.2024 12:08

Или Task.Run(() => File.ReadLines(filePath).Last());?

Enigmativity 28.03.2024 12:09

@Enigmativity, спасибо за ответ. Использование File.ReadLines() вызывает это исключение - обновлен вопрос выше.

Astroboy 28.03.2024 12:13

Ну давай же. Вы действительно читали это сообщение об ошибке? Это не совсем имя вашего файла, не так ли? Похоже, вы добавили одну из строк из файла в путь к папке. Возможно, покажите новый код, который вы использовали, а не только сообщение об ошибке, чтобы мы могли увидеть, что вы сделали не так. Конечно, вы должны были увидеть, что имя файла неверное, и исправить его самостоятельно путем отладки.

jmcilhinney 28.03.2024 12:31

@jmcilhinney, Да, это сообщение об ошибке, которое я прочитал. Я ничего не добавлял. Пожалуйста, найдите обновленный код выше

Astroboy 28.03.2024 12:35

@Astroboy — Код показывает, что имя файла должно быть "/home/adwait/azure-iot-sdk-csharp/iothub/device/samples/sol‌​utions/PnpDeviceSamp‌​les/Robot/Data/Robot‌​s_data.csv", и оно сильно отличается от пути в исключении. Пожалуйста, покажите нам настоящий код, который вы используете.

Enigmativity 28.03.2024 12:43

@Enigmativity, вот ссылка на полный код. Извини! Я не смог вставить это в раздел выше

Astroboy 28.03.2024 12:47

if (File.Exists(filePath)) строка sFileLine = File.ReadAllLines(filePath).LastOrDefault(); (если код может достичь файла)

Power Mouse 28.03.2024 12:50

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

derpirscher 28.03.2024 12:59
new StreamReader(File.ReadLInes(filePath).Last())) читает последнюю строку из файла и интерпретирует ее содержимое как имя файла для конструктора StreamReader.
Klaus Gütter 28.03.2024 13:28

Если вы вызываете File.ReadLines, а вам следует это сделать, вы НЕ используете StreamReader. Подумайте о том, что вы пишете. Как вы думаете, что делает File.ReadLines? Он читает строки файла. Как вы думаете, что делает Ласт? Он получает последний элемент последовательности; в данном случае последняя строка файла. Зачем тогда передавать последнюю строку в StreamReader?

jmcilhinney 28.03.2024 15:10

@jmcilhinney, точная передача последней строки в программу чтения потока не имеет смысла, потому что она уже читается функцией ReadLines(), которая теперь указывает на последнюю строку в CSV.

Astroboy 28.03.2024 15:37

И вы ошиблись в коде, который я разместил в комментариях. File.ReadLInes(filePath).Last() неправильно. Вам следует копировать и вставлять, а не перепечатывать. Вы также должны публиковать только тот код, который вы действительно запустили. В противном случае вы просите нас исправить основные синтаксические ошибки.

Enigmativity 29.03.2024 00:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
13
80
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Медленный (но надежный) метод — прочитать все строки файла и вернуть последнюю. Что-то вроде:

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-файл. Я думаю, что этот подход может сработать.

Astroboy 28.03.2024 13:45

Сохранение последней позиции чтения, быстрая перемотка назад и последующее чтение только того, что нового, — это гораздо лучший подход, чем повторное чтение всех данных. Что, если с момента последнего анализа было добавлено более одной строки?

Sinatr 28.03.2024 16:46

@Charlieface Вы должны сохранить предыдущую строку, так как последнее чтение — null.

Corey 28.03.2024 21:24

@Sinatr Второй вариант переходит прямо примерно к последней строке.

Charlieface 28.03.2024 22:33

@Charlieface Последний ReadLine() сбрасывает переменную line на null. Вот почему вам необходимо сохранить предыдущее значение в цикле.

Corey 28.03.2024 22:34

@Sinatr Да, если добавить несколько строк, будет прочитана только последняя. В конце концов, это был вопрос... и единственный вариант тривиального метода «прочитать последнюю строку файла». Хуже того, это всегда будет возвращать последнюю строку, независимо от изменений или нет. Добавлю нетривиальное решение, которое гораздо полезнее.

Corey 28.03.2024 22:37

Ах, ты прав

Charlieface 28.03.2024 22:37

@Sinatr Лучше?

Corey 28.03.2024 23:22

Другие вопросы по теме