最近在使用FtpWebResponse.GetResponseStream方法时遇上个问题——Stream在未关闭之前就报出了ObjectDisposedException。刚开始十分困惑,因为一直用的是类似的写法,从逻辑上不应该会出现异常。之后通过ILSpy工具查看源代码以及网上找寻相关资料才找到了原因,故在此作文,以作分享。

我最初的代码是这样写的:

FtpWebRequest request = (FtpWebRequest)WebRequest.Create(param.URL);
request.Credentials = new NetworkCredential(param.User, param.PassWord);
request.Method = WebRequestMethods.Ftp.DownloadFile; using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
{
using (StreamReader r = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding("gb2312")))
{
List<string> data = new List<string>();
string line = r.ReadLine();
while (line != null)
{
data.Add(line);
line = r.ReadLine();
}
}
}

想法是按行读取下载的文件并保存在一个List中,以作他用。

但这段代码在读取某些文件的时候出错了, line = r.ReadLine(); 执行至此行代码时会抛出ObjectDisposedException异常。

这十分奇怪,因为从代码逻辑上看Stream在此时显然仍未被关闭。

但在查看源代码后,豁然发现我错了,FtpDataStream在读取时是会自己关闭Stream的。

FtpDataStream的Read方法源码:

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

当其内部NetworkStream读取的结果为0时,其本身会自动Close。*这是FtpDataStream特有的操作方式,其它Stream不会这样做。

但如果仅是这样的话,还是无法解释异常的出现,因为若是NetworkStream读取不到字节的情况下,循环中的判断 while (line != null) 也应该能保证不再执行ReadLine操作。

那么再看Stream的ReadLine方法源码:

public override string ReadLine()
{
if (this.stream == null)
{
__Error.ReaderClosed();
}
this.CheckAsyncTaskInProgress();
if (this.charPos == this.charLen && this.ReadBuffer() == 0)
{
return null;
}
StringBuilder stringBuilder = null;
int num;
char c;
while (true)
{
num = this.charPos;
do
{
c = this.charBuffer[num];
if (c == '\r' || c == '\n')
{
goto IL_4A;
}
num++;
}
while (num < this.charLen);
num = this.charLen - this.charPos;
if (stringBuilder == null)
{
stringBuilder = new StringBuilder(num + 80);
}
stringBuilder.Append(this.charBuffer, this.charPos, num);
if (this.ReadBuffer() <= 0)
{
goto Block_11;
}
}
IL_4A:
string result;
if (stringBuilder != null)
{
stringBuilder.Append(this.charBuffer, this.charPos, num - this.charPos);
result = stringBuilder.ToString();
}
else
{
result = new string(this.charBuffer, this.charPos, num - this.charPos);
}
this.charPos = num + 1;
if (c == '\r' && (this.charPos < this.charLen || this.ReadBuffer() > 0) && this.charBuffer[this.charPos] == '\n')
{
this.charPos++;
}
return result;
Block_11:
return stringBuilder.ToString();
}

此方法中不会抛出异常,但若是执行到其中的ReadBuffer方法代码,就会调用内部成员stream的Read方法,即FtpDataStream的Read方法,则可能会出现问题。

使用ReadBuffer方法的地方共有两处,第一处在完成读取文件时一定会被调用,ReadLine在此处返回null,以说明所有读取操作已经完成,不要再继续读文件。此处被执行一次且只被执行一次。(在加了类似"line != null"判断的前提下)

第二处就不一样了,如果行末是'\r'或\n'字符结尾的话,则第二处ReadBuffer方法不会被执行。

若仅执行一次ReadBuffer,即只调用一次FtpDataStream的Read方法,那么即使发生最糟的结果,Stream关闭,也不会抛出异常。而如果被执行了两次,并且第一次时已读不到字节,那么由于Stream会被关闭,所以必然会出现异常。

仔细看一下 while (true) 循环中的代码,在行末不是'\r'或\n'字符结尾的场合,只有ReadBuffer的返回结果是小于等于零的前提下才能退出这个循环,即FtpDataStream必定被Close。于是当最后一次读取文件时,因为必定要执行第一处的ReadBuffer方法,那么抛出异常便是无法避免的。

所以到了这里,可以大胆判断只有文件末尾没有EOL标识的才会出现因提前关闭Stream而产生的异常。(EOL指\r,\n这样的行结束标识符)

对比了下运行成功的文件与失败的文件内容,果然失败的文件末是没有\r\n的。

当找到原因后解决问题就简单多了,网上即有现成的方法,创建继承Stream的子类,然后重写ReadLine方法:

public override String ReadLine()
{
try
{
return base.ReadLine();
}
catch (ObjectDisposedException ode)
{
return null;
}
}

或者更简单的改法,直接在原来的代码上加try/catch:

string line = r.ReadLine();
while (line != null)
{
data.Add(line);
try
{
line = r.ReadLine();
}
catch(ObjectDisposedException ode)
{
line = null;
}
}

如果不喜欢这样的解决方法,也可以弃用ReadLine方法,改为使用ReadToEnd方法。在读完文件后通过Split方法再保存为List。

string allData = r.ReadToEnd();
List<string> data = allData.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).ToList();

最后需要指出的是,这个问题确实是个bug,而且早在2010年就已被用户发现并提交给微软,但最终的结果是微软不会修复这个bug——

"Thank you for your feedback. You are correct, Read does call Close when it will return 0. We have examined this issue and determined that fixing it would have a negative impact on existing applications."

https://connect.microsoft.com/VisualStudio/feedback/details/594446/ftpdatastream-read-closes-stream

FtpDataStream中的隐藏问题的更多相关文章

  1. c++中的隐藏、重载、覆盖(重写)

    转自c++中的隐藏.重载.覆盖(重写) 1 重载与覆盖 成员函数被重载的特征: (1)相同的范围(在同一个类中): (2)函数名字相同: (3)参数不同: (4)virtual关键字可有可无. 覆盖是 ...

  2. C++中名字隐藏,名字查找优先于类型检查

    题目 C++中名字隐藏是什么? 解答 让我们通过一个例子来讲解C++中的名字隐藏.在C++中,如果一个类里有一个重载的方法, 你用另一个类去继承它并重写(覆盖)那个方法.你必须重写所有的重载方法, 否 ...

  3. 通过iframe在其父窗口中打开隐藏元素

    先上代码 $(".login-box,#dl,.top_01 a:eq(1)").click(function(){ if(self!=top){ parent.$("# ...

  4. excel中如何隐藏列和取消隐藏列

    https://jingyan.baidu.com/article/148a192191dc9a4d71c3b11c.html excel如何隐藏列 1 先看下原表格是怎么样的. 2 隐藏列方法一:首 ...

  5. c++中的隐藏及重载、重写与隐藏的区别

    c/c++中的隐藏  举个栗子 class A { public : void fun1(int a, int b) { cout<<"abcd"<<end ...

  6. 关于在C#中对类中的隐藏基类方法和重写方法的理解

    最近在学习C#,在C#中的类看到重写和隐藏基类的方法这些概念.才开始感觉自己不是很理解这些概念.也区分不开这些概念.通过自己的查找资料和练习后.慢慢的理解了类中的隐藏和重写这个概念.在C#中只有在基类 ...

  7. 在ChemDraw中一键隐藏所有氢原子的方法

    在常见的化学结构中氢原子是非常常见的一种原子,而且在很多的结构中氢原子的数量是非常的多的.因此我们在使用ChemDraw化学绘图软件绘制化学结构的过程中,发现有的时候氢原子数量过多会影响到整体结构的美 ...

  8. DjVu、PDF中的隐藏文本

    作者:马健邮箱:stronghorse_mj@hotmail.com发布:2012.06.11 目录一.背景二.DjVu中的隐藏文本三.PDF中的隐藏文本 一.背景 目前对于扫描电子文档,网上比较流行 ...

  9. 校对双层PDF中的隐藏文本

    作者:马健邮箱:stronghorse_mj@hotmail.com发布:2012.06.11 目录一.背景二.能够校对的PDF需要满足的条件三.校对工具的选择四.校对过程五.延伸讨论 事先声明:本文 ...

随机推荐

  1. eclipse无法创建Server

    报错:Cannot create a server using the selected type1.退出eclipse 2.到[工程目录下]/.metadata/.plugins/org.eclip ...

  2. 兼容ie8 rgba()用法

    今天遇到了一个问题,要在一个页面中设置一个半透明的白色div.这个貌似不是难题,只需要给这个div设置如下的属性即可: background: rgba(255,255,255,.1); 但是要兼容到 ...

  3. getElementsByTagName获得的不是数组的问题!

    getElementsByTag() returns a NodeList instead of an Array. You can convert a NodeList to an Array bu ...

  4. CS0234: 命名空间“System.Web.Mvc”中不存在类型或命名空间名称“Html、Ajax”(是否缺少程序集引用?)

    从SVN上down下来的程序,编译报了一大堆的错,发现是缺少引用,但是明明引用了,后来打开引用,发现system.web.mvc这个引用打着叹号,如图: 后来重新引用了本机的system.web.mv ...

  5. linq group by max 多表链接实例

    SELECT s.* FROM dbo.ERG_TipOffsInfo s, (SELECT Data,MAX(Createtime) max_Time FROM dbo.ERG_TipOffsInf ...

  6. asp.net(C#) Excel导出类 导出.xls文件

    ---恢复内容开始--- using Microsoft.Office.Interop.Excel; 针对office 2003需添加引用Microsoft   Excel   11.0   Obje ...

  7. python ImportError: DLL load failed: %1 不是有效的 Win32 应用程序

    导入的时候报出了 ImportError 在windows上安装python 的模块后,导入模块时报 python ImportError: DLL load failed: %1 不是有效的 Win ...

  8. jsp_内置对象_request

    request内置对象是使用最多的一个对象,其主要作用是接收客户端发送来的请求信息.如请求的参数.发送的头信息等都属于客户端发送来的信息.request是javax.servlet.http.Http ...

  9. Word 2007 文档结构图混乱

    Word 2007在编写大型文档时经常出现文档结构图混乱的情况,经过多番检索试验,得出结论: 绝对有效的临时性解决方案:在打开word的时候左下角会有提示word自动更新文档样式,按esc键取消,然后 ...

  10. 网页加载图片原理<转>

    http://www.haorooms.com/post/web_http_request 图片的http请求,有很多种情况,那么究竟什么情况下面不会发生请求呢?下面我用案例一一列举一下,希望对你深入 ...