StreamReader.ReadLine会在一个无限循环中挂起。

12

我有一个简单的程序,使用StreamReader读取文件并逐行处理。但是我正在读取的文件有时可能位于网络文件夹中。在对这样的文件进行一些测试时,我遇到了一个问题:如果在读取过程中某个时刻丢失了网络连接,那么StreamReader将一直停留在同一行,并通过从流中读取相同的行来循环无限次。

有没有办法从流本身找出fileHandle不可用的情况?当StreamReader丢失文件句柄时,我希望会触发类似FileNotAvailableException的异常。

这是我的代码片段...

        string file = @"Z://1601120903.csv"; //Network file
        string line;
        StringBuilder stb = new StringBuilder();      
        StreamReader stream = new StreamReader(file, Encoding.UTF8, true, 1048576);
        do
        {
            line = stream.ReadLine();
            // Do some work here
        } while (line != "");

3
StreamReader 无法“失去”句柄。如果它实际上不能使用缓存数据并且必须连接网络来获取文件内容,则底层的 ReadFile() 调用将失败,您将得到一个 System.IOException。该异常报告“网络错误”,当读取文件时可能出现的众多问题之一。您需要捕获此异常并向用户报告问题,以便用户可以采取必要的措施来纠正问题。 - Hans Passant
1
我正在考虑关闭这个问题,因为这里有一个错误,在这段代码中并不明显。这里展示的代码必须是真实代码的简化版本。 - usr
@HansPassant,是的,我最初也是这么想的。但如果你执行这行代码,你会遇到问题。它不会抛出异常,因为它一直以同样的行无限地重复。 - Asanka
1
你应该负责处理错误。你所抱怨的问题很简单,就是你的代码片段没有任何错误处理。 - Hans Passant
3
你肯定可以把它包装错。如果您展示真实的代码,那么这将无限地更容易,因为我们不必一直猜测它。 - Hans Passant
显示剩余2条评论
6个回答

19

null 比较而不是与 空字符串 比较:

https://msdn.microsoft.com/zh-cn/library/system.io.streamreader.readline(v=vs.110).aspx

返回值类型:System.String 输入流的下一行,如果达到输入流的末尾则为 null。

    do
    {
        line = stream.ReadLine();
        // Do some work here
    } while (line != null);
然而,更好的方法是让.Net为您完成(逐行文件读取)所有工作,并放弃所有的读取器:
  foreach (String line in File.ReadLines(file)) {
    // Do some work here
  }

1
使用类似于 string.IsNullOrEmpty(line) 这样的方法是否会改善代码? - terbubbs
5
不,一个“空字符串”可以出现在文件的中间。 - Dmitry Bychenko
1
@DmitryBychenko,File.ReadLines会将所有行读入内存,而stream.ReadLine会依次逐行读取,而不会影响内存。将所有行读入内存对我来说不是一个选择,因为文件本身可能相当大。 - Asanka
3
@Asanka:请注意,我使用的是File.ReadLines而不是File.ReadAllLines;而且File.ReadLines不会将所有行加载到内存中。 - Dmitry Bychenko
@DmitryBychenko,我明白了,谢谢你的信息。我之前没有接触过这种方法,我会尝试一下并告诉你结果。 - Asanka
显示剩余2条评论

8

正确的方法1 (EndOfStream) :

using(StreamReader sr = new StreamReader(...)) {
    while(!sr.EndOfStream) {
        string line = sr.ReadLine();
        Console.WriteLine(line);
    }
}

正确的方法2 (Peek)

using(StreamReader sr = new StreamReader(...)) {
    while(sr.Peek() >= 0) {
        string line = sr.ReadLine();
    }
}

注意:将空字符串视为文件结束是不正确的。
如果在读取过程中网络连接丢失,那么它将会停留在同一行上并一遍又一遍地循环,结果从stream.ReadLine()中得到的仍然是相同的行。
我现在已经检查了这种情况-在这种情况下应该抛出System.IO.IOException("未找到网络路径")异常。
用try catch块包装这个代码段将不能解决我的问题,对吗?
在这种情况下,你可以通过以下方式中断读取。
string line;
do {
    try {
        line = sr.ReadLine();
        // Do some work here
    }
    catch(System.IO.IOException) {
        break; 
    }
} while(line != null);

我检查了这两个属性。当由于网络问题而放下文件句柄时,这两个条件都将返回true,并且stream.ReadLine()将同一行输出到变量line中。 - Asanka
1
奇怪,它没有抛出任何异常。你在文件读取操作中断开了连接吗? - Asanka
@Asanka 我也测试了断开映射驱动器并抛出异常,但只有在读取缓冲区后(OP设置为1048576的缓冲区)才会抛出异常。我认为asanka有一个包含重复行的CSV文件,当他们断开网络连接时,他们只是吐出缓冲区而不等待它到达缓冲区末尾抛出异常。将缓冲区降低到100,然后再试一次。 - Quantic
@DmitryG,我会使用相对较小的缓冲区大小进行检查...谢谢您提供的信息。 - Asanka
在我看来,你不应该使用异常处理来控制应用程序的流程。 - Jamie Rees
显示剩余2条评论

4
如果你使用 while 循环来编写它:
while ((line = sr.ReadLine()) != null)
{
    Console.WriteLine(line);
}

Source


1

还有一种方法是使用File.ReadAllLines(),它会负责打开文件、读取所有行、关闭文件,并可能处理网络连接丢失的情况。

var lines = File.ReadAllLines("Z://1601120903.csv");

foreach(line in lines)
{
 // Do some work
}

0

将文件本地复制似乎是一个不错的解决方案。但是文件本身可能相当大。 - Asanka
1
这并没有解决他所面临的问题。他错误地假设同一行可能会被重复输出。相反,这是他代码中的一个漏洞。 - usr
@Asanka,如我所述,请检查是否可以复制文件,如果您仍然想要跟踪当前读取器索引,则应使用我添加的解决方案。 - Felix Av

0

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