如何避免使用c#File.ReadLines First()锁定文件

时间:2015-05-06 15:55:22

标签: c# locking readlines

我不想在任何时候阅读整个文件,我知道这个问题有答案,我想要 o阅读第一行或最后一行。

我知道我的代码锁定了它正在读取的文件有两个原因:1)当我使用此代码运行我的小应用程序时,写入文件的应用程序会间歇性地崩溃,但是当我没有运行此代码时,它永远不会崩溃! 2)有一些文章会告诉你File.ReadLines会锁定文件。

有一些类似的问题,但答案似乎涉及读取整个文件,这对大文件来说很慢,因此不是我想要做的。我要求大部分时间只阅读最后一行也是我所读到的独特之处。

我知道如何读取第一行(标题行)和最后一行(最新行)。我不希望在我的代码中的任何一点读取所有行,因为这个文件会变得很大并且读取整个文件会变慢。

我知道

line = File.ReadLines(fullFilename).First()。Replace(“\”“,”“);

...与...相同

FileStream fs = new FileStream(@fullFilename,FileMode.Open,FileAccess.Read,FileShare.Read);

我的问题是,如何重复读取文件的第一行和最后一行,这些行可能是由另一个应用程序写入而不以任何方式锁定它。我无法控制写入文件的应用程序。它是一个可以随时附加的数据日志。我这样听的原因是这个日志可以连续几天附加。我希望在我自己的c#程序中看到此日志中的最新数据,而无需等待日志完成写入。

我的代码用于调用阅读/收听功能......

    //Start Listening to the "data log"
    private void btnDeconstructCSVFile_Click(object sender, EventArgs e)
    {
        MySandbox.CopyCSVDataFromLogFile copyCSVDataFromLogFile = new MySandbox.CopyCSVDataFromLogFile();
        copyCSVDataFromLogFile.checkForLogData();
    }

我的班级听力。目前它只是将数据添加到2个泛型列表中......

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MySandbox.Classes;
using System.IO;

namespace MySandbox
{
    public class CopyCSVDataFromLogFile
    {
        static private List<LogRowData> listMSDataRows = new List<LogRowData>();
        static String fullFilename = string.Empty;
        static LogRowData previousLineLogRowList = new LogRowData();
        static LogRowData logRowList = new LogRowData();
        static LogRowData logHeaderRowList = new LogRowData();
        static Boolean checking = false;

        public void checkForLogData()
        {
            //Initialise
            string[] logHeaderArray = new string[] { };
            string[] badDataRowsArray = new string[] { };

            //Get the latest full filename (file with new data) 
            //Assumption: only 1 file is written to at a time in this directory.
            String directory = "C:\\TestDir\\";
            string pattern = "*.csv";
            var dirInfo = new DirectoryInfo(directory);
            var file = (from f in dirInfo.GetFiles(pattern) orderby f.LastWriteTime descending select f).First();
            fullFilename = directory + file.ToString(); //This is the full filepath and name of the latest file in the directory!

            if (logHeaderArray.Length == 0)
            {
                //Populate the Header Row
                logHeaderRowList = getRow(fullFilename, true);
            }

            LogRowData tempLogRowList = new LogRowData();

            if (!checking)
            {
                //Read the latest data in an asynchronous loop
                callDataProcess();
            }
        }

        private async void callDataProcess()
        {
            checking = true;                        //Begin checking
            await checkForNewDataAndSaveIfFound();
        }

        private static Task checkForNewDataAndSaveIfFound()
        {
            return Task.Run(() =>   //Call the async "Task"
            {
                while (checking)    //Loop (asynchronously)
                {
                    LogRowData tempLogRowList = new LogRowData();

                    if (logHeaderRowList.ValueList.Count == 0)
                    {
                        //Populate the Header row
                        logHeaderRowList = getRow(fullFilename, true);
                    }
                    else
                    {
                        //Populate Data row
                        tempLogRowList = getRow(fullFilename, false);

                        if ((!Enumerable.SequenceEqual(tempLogRowList.ValueList, previousLineLogRowList.ValueList)) &&
                            (!Enumerable.SequenceEqual(tempLogRowList.ValueList, logHeaderRowList.ValueList)))
                        {
                            logRowList = getRow(fullFilename, false);
                            listMSDataRows.Add(logRowList);
                            previousLineLogRowList = logRowList;
                        }
                    }

                    //System.Threading.Thread.Sleep(10);  //Wait for next row.
                }
            });
        }

        private static LogRowData getRow(string fullFilename, bool isHeader)
        {
            string line;
            string[] logDataArray = new string[] { };
            LogRowData logRowListResult = new LogRowData();

            try
            {
                if (isHeader)
                {
                    //Asign first (header) row data.
                    //Works but seems to block writting to the file!!!!!!!!!!!!!!!!!!!!!!!!!!!
                    line = File.ReadLines(fullFilename).First().Replace("\"", "");    
                }
                else
                {
                    //Assign data as last row (default behaviour).
                    line = File.ReadLines(fullFilename).Last().Replace("\"", "");
                }

                logDataArray = line.Split(',');

                //Copy Array to Generics List and remove last value if it's empty.
                for (int i = 0; i < logDataArray.Length; i++)
                {
                    if (i < logDataArray.Length)
                    {
                        if (i < logDataArray.Length - 1)
                        {
                            //Value is not at the end, from observation, these always have a value (even if it's zero) and so we'll store the value.
                            logRowListResult.ValueList.Add(logDataArray[i]);
                        }
                        else
                        {
                            //This is the last value
                            if (logDataArray[i].Replace("\"", "").Trim().Length > 0)
                            {
                                //In this case, the last value is not empty, store it as normal.
                                logRowListResult.ValueList.Add(logDataArray[i]);
                            }
                            else { /*The last value is empty, e.g. "123,456,"; the final comma denotes another field but this field is empty so we will ignore it now. */ }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                if (ex.Message == "Sequence contains no elements")
                { /*Empty file, no problem. The code will safely loop and then will pick up the header when it appears.*/ }
                else
                {
                    //TODO: catch this error properly
                    Int32 problemID = 10;   //Unknown ERROR.
                }
            }

            return logRowListResult;
        }
    }
}

1 个答案:

答案 0 :(得分:0)

我在其他问题的组合中找到了答案。一个答案解释了如何从文件的末尾读取,我对其进行了调整,使其只读取文件末尾的1行。另一个解释了如何在不锁定的情况下读取整个文件(我不想读取整个文件但是非锁定部分很有用)。所以现在你可以读取文件的最后一行(如果它包含行尾字符)而不锁定它。对于行结束的另一端,只需用你的行尾字符字节替换我的10和13 ......

将以下方法添加到公共类CopyCSVDataFromLogFile

    private static string Reverse(string str)
    {
        char[] arr = new char[str.Length];
        for (int i = 0; i < str.Length; i++)
            arr[i] = str[str.Length - 1 - i];
        return new string(arr);
    }

并替换此行...

line = File.ReadLines(fullFilename).Last().Replace("\"", "");

使用此代码块...

                Int32 endOfLineCharacterCount = 0;
                Int32 previousCharByte = 0;
                Int32 currentCharByte = 0;
                //Read the file, from the end, for 1 line, allowing other programmes to access it for read and write!
                using (FileStream reader = new FileStream(fullFilename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 0x1000, FileOptions.SequentialScan))
                {
                    int i = 0;
                    StringBuilder lineBuffer = new StringBuilder();
                    int byteRead;
                    while ((-i < reader.Length) /*Belt and braces: if there were no end of line characters, reading beyond the file would give a catastrophic error here (to be avoided thus).*/
                        && (endOfLineCharacterCount < 2)/*Exit Condition*/)
                    {
                        reader.Seek(--i, SeekOrigin.End);
                        byteRead = reader.ReadByte();

                        currentCharByte = byteRead;

                        //Exit condition: the first 2 characters we read (reading backwards remember) were end of line (). 
                        //So when we read the second end of line, we have read 1 whole line (the last line in the file)
                        //and we must exit now.
                        if (currentCharByte == 13 && previousCharByte == 10)
                        {
                            endOfLineCharacterCount++;
                        }

                        if (byteRead == 10 && lineBuffer.Length > 0)
                        {
                            line += Reverse(lineBuffer.ToString());
                            lineBuffer.Remove(0, lineBuffer.Length);
                        }
                        lineBuffer.Append((char)byteRead);

                        previousCharByte = byteRead;
                    }

                    reader.Close();
                }