如何在不锁定文件的情况下读取文本文件?

112

我有一个将日志以简单格式写入文本文件的Windows服务。

现在,我想创建一个小应用程序来读取服务的日志,并显示现有日志和添加的日志作为实时视图。

问题是,服务锁定文本文件以添加新行,同时查看器应用程序锁定文件以进行读取。

服务代码:

void WriteInLog(string logFilePath, data)
{
    File.AppendAllText(logFilePath, 
                       string.Format("{0} : {1}\r\n", DateTime.Now, data));
}

观察者代码:

int index = 0;
private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                using (StreamReader sr = new StreamReader(logFilePath))
                {
                    while (sr.Peek() >= 0)  // reading the old data
                    {
                        AddLineToGrid(sr.ReadLine());
                        index++;
                    }
                    sr.Close();
                }

                timer1.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }


private void timer1_Tick(object sender, EventArgs e)
        {
            using (StreamReader sr = new StreamReader(logFilePath))
            {
                // skipping the old data, it has read in the Form1_Load event handler
                for (int i = 0; i < index ; i++) 
                    sr.ReadLine();

                while (sr.Peek() >= 0) // reading the live data if exists
                {
                    string str = sr.ReadLine();
                    if (str != null)
                    {
                        AddLineToGrid(str);
                        index++;
                    }
                }
                sr.Close();
            }
        }

我的读写代码有问题吗?

如何解决这个问题?


7个回答

142

你需要确保服务和读取器都以非排他方式打开日志文件。尝试这样做:

对于服务(即你示例中的写入器),使用以下方式创建的FileStream实例:

var outStream = new FileStream(logfileName, FileMode.Open, 
                               FileAccess.Write, FileShare.ReadWrite);

对于读者,请使用相同的方法,但更改文件访问权限:

var inStream = new FileStream(logfileName, FileMode.Open, 
                              FileAccess.Read, FileShare.ReadWrite);

另外,由于 FileStream 实现了 IDisposable 接口,请确保在两种情况下都考虑使用 using 语句,例如对于写入器:

using(var outStream = ...)
{
   // using outStream here
   ...
}

祝你好运!


还有一个ReadLines()版本,网址是https://dev59.com/BG435IYBdhLWcg3wkBBe。 - Colin
完美 - 我曾经认为使用File.Open()和FileAccess.Read就足够了,但事实并非如此。不过这个方法是可以的。 :) - neminem
在写入方面,由于只有一个写入者,FileShare.Read是否足够? - crokusek
2
值得注意的是,FileStream 实现了 IDisposable 接口。 - weeksdev

33

在读取文本文件时,明确设置共享模式。

using (FileStream fs = new FileStream(logFilePath, 
                                      FileMode.Open, 
                                      FileAccess.Read,    
                                      FileShare.ReadWrite))
{
    using (StreamReader sr = new StreamReader(fs))
    {
        while (sr.Peek() >= 0) // reading the old data
        {
           AddLineToGrid(sr.ReadLine());
           index++;
        }
    }
}

2
不需要显式关闭流,因为 StreamReader.Dispose 会自动关闭它。 - nothrow
2
我觉得文件共享应该在读写流上都设置而不仅仅是只读。 - L.E.O
StreamReader.Dispose 如果我没记错的话,也会处理 FileStream 的释放。这里进行了两次释放。 - Eregrith

15
new StreamReader(File.Open(logFilePath, 
                           FileMode.Open, 
                           FileAccess.Read, 
                           FileShare.ReadWrite))

-> 这不会锁定文件。


1
读取数据时似乎可以工作。当写入时,会出现锁定文件错误。 - webzy
@webzy 当然。在不锁定文件的情况下,您无法将内容写入文件。 - Fandango68
@Fandango68 接受的答案比我的更详细地阐述了解决方案,但是你的说法完全不正确。可以在不持有任何锁的情况下写入文件。 - nothrow
!! 很惊讶。我应该试一下。那么 Windows 如何在运行上述操作的同时实时更新日志文件呢? - Fandango68

10

问题在于当你将内容写入日志时,会独占锁定该文件,因此你的StreamReader将无法打开它。

你需要尝试以只读模式打开文件。

using (FileStream fs = new FileStream("myLogFile.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
    using (StreamReader sr = new StreamReader(fs))
    {
        while (!fs.EndOfStream)
        {
            string line = fs.ReadLine();
            // Your code here
        }
    }
}

据我所知,File.ReadAllText()在只读模式下会失败。 - bohdan_trotsenko
@modosansreves,是的,我已经删除了答案中的那部分内容,因为文档指出,如果文件是只读的,它将抛出UnauthorizedAccessException异常 - 在回答时可能错过了这一点! - James

5

我记得几年前也做过同样的事情。经过一些谷歌查询,我找到了这个:

    FileStream fs = new FileStream(@”c:\test.txt”, 
                                   FileMode.Open, 
                                   FileAccess.Read,        
                                   FileShare.ReadWrite);

即使用FileShare.ReadWrite属性在FileStream()上。(发现在Balaji Ramesh的博客上)

1

你尝试过复制文件,然后读取它吗?

只需在进行重大更改时更新副本即可。


2
我已经尝试过这个方法,但并不是最佳解决方案。复制文件太耗时了,对于一个4MB的文件需要大约20秒的时间。 - Seichi

-4

这个方法可以帮助你最快速地读取文本文件,而且不会锁定它。

private string ReadFileAndFetchStringInSingleLine(string file)
    {
        StringBuilder sb;
        try
        {
            sb = new StringBuilder();
            using (FileStream fs = File.Open(file, FileMode.Open))
            {
                using (BufferedStream bs = new BufferedStream(fs))
                {
                    using (StreamReader sr = new StreamReader(bs))
                    {
                        string str;
                        while ((str = sr.ReadLine()) != null)
                        {
                            sb.Append(str);
                        }
                    }
                }
            }
            return sb.ToString();
        }
        catch (Exception ex)
        {
            return "";
        }
    }

希望这个方法能对你有所帮助。

5
据我所理解,这种方法会将整个文件读入一个字符串,并在此过程中忽略异常。我无法看出这如何有助于文件锁定。 - default locale

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