StreamReader ReadLine 抛出异常而不是返回 null

StreamReader ReadLine throwing disposed exception rather than returning null

我正在尝试从 FTP 服务器获取文本文件,然后逐行读取它,将每一行添加到列表中。

我的代码似乎符合标准模式:

            var response = (FtpWebResponse)request.GetResponse();
            using (var responseStream = response.GetResponseStream())
            {
                using (var reader = new StreamReader(responseStream))
                {
                    string line;
                    while((line = reader.ReadLine()) != null)
                    {
                        lines.Add(line);
                    }
                }
            }

但是,由于某种原因,当在文件的最后一行调用 reader.ReadLine() 时,它抛出异常,显示 "Cannot access a disposed object".

这真让我感到奇怪。如果我没记错的话,当没有更多数据时,流的最后一行是 null,对吗?

此外(虽然我不确定),这似乎只发生在本地;该服务的实时版本似乎运行良好(尽管有一些问题我正试图弄清楚)。我当然没有在我的日志中看到这个问题。

有人有想法吗?

编辑:这是异常的全文。

System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.Sockets.NetworkStream'.
   at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)
   at System.Net.FtpDataStream.Read(Byte[] buffer, Int32 offset, Int32 size)
   at System.IO.StreamReader.ReadBuffer()
   at System.IO.StreamReader.ReadLine()
   at CENSORED.CatalogueJobs.Common.FtpFileManager.ReadFile(String fileName, String directory) in C:\Projects\CENSORED_CatalogueJobs\CENSORED.CatalogueJobs.Common\FtpFileManager.cs:line 104
   at CENSORED.CatalogueJobs.CENSOREDDispatchService.CENSOREDDispatchesProcess.<Process>d__12.MoveNext() in C:\Projects\CENSORED_CatalogueJobs\CENSORED.CatalogueJobs.CENSOREDDispatches\CENSOREDDispatchesProcess.cs:line 95"

类型是 "System.ObjectDisposedException"。抱歉审查,例外包含我客户的名字。

编辑 2:现在是代码,在扩展并删除了一层使用之后(我想我做对了)。

            var response = (FtpWebResponse)request.GetResponse();
            using (var reader = new StreamReader(response.GetResponseStream()))
            {
                string line = reader.ReadLine();
                while(line != null)
                {
                    lines.Add(line);
                    line = reader.ReadLine();
                }
            }

编辑 3:代码的更广泛视图(为了我的理智暂时恢复)。这基本上是函数中的所有内容。

            var request = (FtpWebRequest)WebRequest.Create(_settings.Host + directory + fileName);

            request.Method = WebRequestMethods.Ftp.DownloadFile;
            request.Credentials = new NetworkCredential(_settings.UserName, _settings.Password);
            request.UsePassive = false;
            request.UseBinary = true;

            var response = (FtpWebResponse)request.GetResponse();
            using (var responseStream = response.GetResponseStream())
            {
                using (var reader = new StreamReader(responseStream))
                {
                    string line;
                    while((line = reader.ReadLine()) != null)
                    {
                        lines.Add(line);
                    }
                }
            }

我认为正在处理的是响应流。

你不应该在它周围加上 using 语句。流资源属于 FtpWebResponse 对象。

尝试删除它,看看问题是否消失。

您还可以通过扩展代码来为自己和他人提供服务,以便您可以正确地单步执行它。它可能会揭示其他内容:

using (var reader = new StreamReader(responseStream))
{
    string line = reader.ReadLine();
    while(line != null)
    {
        lines.Add(line);
        line = reader.ReadLine();
    }
}

多了一行代码,可读性更强,更容易调试。

检查内部 FtpDataStream class shows that its Read 方法的来源将 关闭 如果没有更多字节,流将自行关闭:

    public override int Read(byte[] buffer, int offset, int size) {
        CheckError();
        int readBytes;
        try {
            readBytes = m_NetworkStream.Read(buffer, offset, size);
        } catch {
            CheckError();
            throw;
        }
        if (readBytes == 0)
        {
            m_IsFullyRead = true;
            Close();
        }
        return readBytes;
    }

Stream.Close() 是直接调用 Dispose :

    public virtual void Close()
    {
        /* These are correct, but we'd have to fix PipeStream & NetworkStream very carefully.
        Contract.Ensures(CanRead == false);
        Contract.Ensures(CanWrite == false);
        Contract.Ensures(CanSeek == false);
        */

        Dispose(true);
        GC.SuppressFinalize(this);
    }

那是不是其他流(例如FileStream.Read)的工作方式。

看起来 StreamReader.ReadLine 正在尝试读取更多数据,导致出现异常。 可能是因为它试图解码文件末尾的 UTF8 或 UTF16 字符。

与其从网络流中逐行读取,不如在读取之前用Stream.CopyTo复制到MemoryStream或FileStream中

更新

这种行为虽然完全出乎意料,但并非没有道理。以下更多是基于 FTP 协议本身、FtpWebRequest.cs and FtpDataStream.cs 来源和尝试下载多个文件的痛苦经历的有根据的猜测。

FtpWebRequest 是 FTP 之上的(非常)漏洞抽象。 FTP 是一个面向连接的协议,具有特定的命令,如 LIST、GET 和缺少的 MGET。这意味着,一旦服务器完成向客户端发送数据以响应 LIST 或 GET,它就会返回等待命令。

FtpWebRequest 试图通过让每个请求看起来是无连接的来隐藏这一点。这意味着一旦客户端完成从响应流中读取数据,就没有有效状态 return 到 - FtpWebResponse 命令 不能 用于发出进一步的命令。它也不能用于通过 MGET 检索 多个 文件,这是一个主要的痛苦。毕竟应该只有 一个 响应流。

随着 .NET Core 和越来越多的需要(委婉地说)使用不受支持的协议,如 SFTP,找到一个更好的 FTP 客户端库可能是个好主意。

我也遇到了这个问题。看起来 FtpDataStream 在关闭后将 CanRead 和 CanWrite 重置为 false。因此,作为一种解决方法,您可以在阅读下一行之前检查 CanRead 以避免 ObjectDisposedException。

using (var responseStream = response.GetResponseStream())
{
    using (var reader = new StreamReader(responseStream))
    {
        string line;
        while(responseStream.CanRead && (line = reader.ReadLine()) != null)
        {
            lines.Add(line);
        }
    }
}