使用计时器逐行读取文本文件

6
StreamReader sr = new StreamReader("C:/CR EZ Test/Log.txt");    //use with IF
private void timer2_Tick(object sender, EventArgs e)
{
    if ((line = sr.ReadLine()) != null)
    {   
        //FileStream fs = File.Open("C:/CR EZ Test/Log.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        //StreamReader sr = new StreamReader(fs); //use with While can't use with }else{
        //while ((line = sr.ReadLine()) != null) 
        //{
        string[] dataLog = line.Split(new[] { ',' }, StringSplitOptions.None);
        mpa = (dataLog[1]);
        ml  = (dataLog[2]);
        lph = (dataLog[3]);
        elapsedTime = float.Parse(dataLog[4]) / 1000;

        if (testStatus > 0) time = elapsedTime.ToString("0.0");
        tb2.Value = int.Parse(dataLog[6]);

        if (chart1.Series[0].Points.Count > tb1.Value && tb1.Value > 0)
        {
            chart1.Series[0].Points.RemoveAt(0);
            chart1.Series[1].Points.RemoveAt(0);
        }
        chart1.Series[0].Points.AddXY(dataLog[5], int.Parse(dataLog[1]));
        chart1.Series[1].Points.AddXY(dataLog[5], int.Parse(dataLog[6]));
        //}
    }
    else
    {
        sr.DiscardBufferedData();
        sr.BaseStream.Seek(0, SeekOrigin.Begin);
        sr.BaseStream.Position = 0;
        //sr.Close();
        //alertTB.Text = "";
        timer2.Enabled = false;
    }
    alertTB.ForeColor = Color.Red;
    alertTB.Text = "Data Log Viewing In Progress";
}

问题在于我通过GUI读取一个充满变量的文本文件,就像重新播放视频一样。按照代码显示的方式,它可以工作并且我可以控制计时器滴答声来改变回放速度。问题是该文件正在使用中,所以在关闭它之前,我无法写入或删除文本。我希望能找到Streamreader的解决方法,或者使用Filestream到Streamreader的代码,使我能够在文件正在使用时编辑它。但问题在于,我无法想出如何使它与计时器配合工作,因为它只会非常快地读取整个文件。非常感谢任何帮助或想法。
这里的问题是如何将注释掉的代码实现以下功能:
  1. 读取文本文件的一行,
  2. 让计时器滴答声,
  3. 然后读取文本文件的下一行,以此类推。显然需要对数据进行处理。

1
有没有读取整个文件,然后使用定时器从内存数据中读取的选项?这样,您只需要确定何时再次读取文件。 - Camilo Terevinto
你知道有没有用这种方式的示例吗? - RatherLogical
尝试使用System.IO.File.ReadAllLines来最小化阅读时间。 - Cleptus
打开文件,一次性将其所有内容读入到一个 List<string> 或其他数据结构中,然后关闭文件,接着处理(“播放”)列表中的内容。当你到达列表末尾时,重新打开文件,读取它,重复上述过程。 - 3Dave
2个回答

4

打开正在使用的文件

我认为你需要使用FileStreamFileShare.ReadWrite来实例化你的StreamReader(不是你已经注释掉的实例)

var fs = new FileStream("C:\foo.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var sr = new StreamReader(fs);

设置流的位置

根据您的评论,似乎您在处理流的位置时遇到了困难,以下是解决方法...

fs.Position = 0; // note this is the FileStream not the StreamReader!
// alternatively, you could use Seek

顺序访问和随机访问的区别

最后,您可能想看一下下面的内容,了解顺序访问和随机访问之间的区别

concept


一个潜在的解决方案

这里有一个名为FileMonitor的类,它将在文件更改/更新时检查文件并更新列表。

我知道您想使用定时器轮询文本文件中的数据,但是如果定时器非常快,我已经优化了FileMonitor,以便在文件更改时仅提取数据。

请注意,根据流的位置,它仅继续读取先前未读取的内容。 因此,如果在“提取”之前删除或修改行,则它将无法工作。 这意味着它仅基于您的要求而运作,并且没有改进来处理许多其他情况,但应该足以满足您的需求。

public class FileMonitor : IDisposable
{
    private readonly FileStream _file;
    private readonly StreamReader _reader;

    private long _position;
    private List<string> _lines;

    public FileMonitor(string file)
    {
        if (String.IsNullOrEmpty(nameof(file))) throw new ArgumentNullException(nameof(file));

        _lines = new List<string>();

        FileSystemWatcher watcher = new FileSystemWatcher();
        watcher.Path = Path.GetDirectoryName(file);
        watcher.Filter = Path.GetFileName(file);
        watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
        watcher.Changed += new FileSystemEventHandler(OnChanged);
        //watcher.Created += new FileSystemEventHandler(OnCreated);
        //watcher.Deleted += new FileSystemEventHandler(OnDeleted);
        //watcher.Renamed += new RenamedEventHandler(OnRenamed);

        // begin watching.
        watcher.EnableRaisingEvents = true;

        // begin reading
        _file = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        _reader = new StreamReader(_file);
        _lines = ReadLines(_reader).ToList();
        _position = _file.Position;
    }

    private void OnChanged(object source, FileSystemEventArgs e)
    {
        List<string> update = ReadLines(_reader).ToList();
         // fix to remove the immidate newline
        if (update.Count() > 0 && String.IsNullOrEmpty(update[0])) update.RemoveAt(0);
        _lines.AddRange(update);
        _position = _file.Position;

        // just for debugging, you should remove this
        Console.WriteLine($"File: {e.FullPath} [{e.ChangeType}]");
    }

    public IEnumerable<string> Lines { get { return _lines; } }

    public void Reset()
    {
        _file.Position = 0;
        _position = _file.Position;
        _lines.Clear(); 
    }

    private static IEnumerable<string> ReadLines(StreamReader reader)
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }

    public void Dispose()
    {
        _reader.Dispose();
        _file.Dispose();
    }
}

这是如何在您的计时器中使用它的方法。
private IEnumerable<string> _lines; // holds all the lines "extracted"

void Main()
{
    string file = @"C:\Data\foo.txt";

    using (var timer = new System.Timers.Timer())
    {
        timer.Interval = 2000; // 2 second interval
        timer.Elapsed += OnTimedEvent; // attach delegate
        timer.Enabled = true; // start the timer    

        // open the file
        using (var monitor = new FileMonitor(file))
        {
            _lines = monitor.Lines;

             // loop forever, remove this
            while (true) { }
        }
    }
}

public void OnTimedEvent(object sender, EventArgs e)
{
    // just for debugging, you should remove this
    Console.WriteLine($"current count: {_lines.Count()}");
}

如果不清楚,提取的数据被保存在一个字符串列表中。你可以使用monitor.Line属性从监视器中获取上述“提取”的数据。

我刚刚尝试了那段代码,但它没有任何作用,只是立即读取文件的末尾。我的最大问题是在读取文本文件的下一行之间实现可变延迟时间,而不会阻塞代码运行。 - RatherLogical
@CRIMSON501 为什么不使用 SeekPosition 并转到流的开头呢? - Svek
@CRIMSON501,我说的是在FileStream上设置Position时与StreamReader不同。 - Svek
我需要找出如何在流读取下一行之间实现定时器滴答声,有什么想法吗? - RatherLogical
@CRIMSON501,抱歉我现在没有时间为你编写解决方案。但是这里应该有足够的信息让你开始着手解决问题。 - Svek
显示剩余7条评论

2

一种已被证明有效的解决方案

 string line;
        if (!File.Exists(logFile))
        {
            viewLog.Text = "Play";
            alertTB.ForeColor = Color.Red;
            alertTB.Text = "File Does Not Exist | Log Data To Create File";
            chart.Text = "Scope On";
        }

        if (File.Exists(logFile))
        {
            var lineCount = File.ReadLines(logFile).Count();//read text file line count to establish length for array
            if (lineCount < 2)
            {
                viewLog.Text = "Play";
                alertTB.ForeColor = Color.Red;
                alertTB.Text = "File Exists | No Data Has Been Recorded";
                chart.Text = "Scope On";
            }

            if (counter < lineCount && lineCount > 0)//if counter is less than lineCount keep reading lines
            {
                line = File.ReadAllLines(logFile).Skip(counter).Take(lineCount).First();

                string[] dataLog = line.Split(new[] { ',' }, StringSplitOptions.None);
                //-----------------------------------------Handling my data 
                counter++;
            }
            else
            {
                counter = 0;
                timer2.Enabled = false;
            }
        }

以下是我得出的解决方案,它允许编辑文件或删除文件内容。在尝试加载文件之前,我获取行数。然后使用计数器迭代每一行。我可以根据计时器间隔改变下一行读取之间的延迟时间,暂停或停止。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接