FtpDataStream中的隐藏问题

最近在使用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中的隐藏问题

时间: 2024-10-12 16:08:50

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

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

题目 C++中名字隐藏是什么? 解答 让我们通过一个例子来讲解C++中的名字隐藏.在C++中,如果一个类里有一个重载的方法, 你用另一个类去继承它并重写(覆盖)那个方法.你必须重写所有的重载方法, 否则未被重写的方法会因为名字相同而被隐藏,从而使它在派生类中不可见. 请看例子: class FirstClass{ public: virtual void MethodA(int); virtual void MethodA(int, int); }; void FirstClass::Metho

在PADS LAYOUT中如何隐藏不需要的鼠线?

如下图示,将net GPR_0的鼠线隐藏. 鼠标右键,选择网络----选择你要隐藏的网络------右键选择view nets----点击对话框右边View List里你所选的网络-----在右下角traces plus the....和None两个打勾,点Apply,就ok了. 在PADS LAYOUT中如何隐藏不需要的鼠线?,布布扣,bubuko.com

CSS圆角效果 -webkit-border-radius(CSS3中border-radius隐藏的威力)

CSS圆角效果 -webkit-border-radius(CSS3中border-radius隐藏的威力) 来源:互联网 作者:佚名 时间:03-28 14:17:14 [大 中 小] border-radius:用这个属性能实现圆角边框的效果.现在只有Mozilla/Firefox 和 Safari 3支持该属性. -webkit-border-radius:苹果:谷歌,等一些浏览器认,因为他们都用的是webkit内核: -moz-border-radius:moz这个属性 主要是专门支持M

Android中的隐藏API和Internal包的使用之获取应用电量排行

今天老大安排一个任务叫我获取手机中应用耗电排行(时间是前天晚上7点到第二天早上10点),所以在网上各种搜索,没想到这种资料还是很多的,发现了一个主要的类:PowerProfile,但是可以的是,这个类没有曝光给我们开发者,所以我们不能调用它 那怎么办呀?,还是有办法的,这个类是藏在:com.android.internal.os.PowerProfile com.android.internal.os.PowerProfile.PowerProfile这个类就是负责解析记录文件,我们可以创建一个

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

最近在学习C#,在C#中的类看到重写和隐藏基类的方法这些概念.才开始感觉自己不是很理解这些概念.也区分不开这些概念.通过自己的查找资料和练习后.慢慢的理解了类中的隐藏和重写这个概念.在C#中只有在基类定义了一些虚方法才能在派生类中重写基类中的虚方法.但是如果在派生类中使用隐藏方法,就不用在基类中定义虚方法.虚方法和重写实现的功能感觉是差不多的.都是在派生类中改变了基类中的方法,但是两者还是有质的区别,概念的性质也是不一样的.   重写是指:将基类中的方法替换掉,也就是抹掉基类中的原有方法,在派生

CAD编辑器中怎么隐藏图纸中标注的尺寸

在建筑设计师们编辑CAD图纸的时候,都会对每一张CAD图纸中的内容进行标注尺寸,但是在我们交给客户查看CAD图纸的时候,标注有尺寸的CAD图纸看起来不是那么的美观,那我们怎么隐藏CAD图纸中标注的尺寸呢?具体要怎么操作?下面小编就教教大家在迅捷CAD编辑器标准版中查看CAD图纸文件时,怎么隐藏图纸中标注的尺寸. 第一步:首先在电脑中任选一个浏览器,在浏览器中搜索迅捷CAD编辑器(标准版),进入官网点击下载最新版本的CAD编辑器,然后双击软件进入到软件的操作窗口. 第二步:编辑器打开之后,在软件顶

LNMP中一些隐藏的安装脚本及目录详解

伏笔VPS一向在用军哥的LNMP一键script搭建站点,使用的人挺多的,而许多人只晓得script是部署Nginx.MySQL/MariaDB.PHP.phpMyAdmin等建站主要环境的,却不晓得该部署包的别的功能script,这里就说下隐蔽的别的软件script及部署目录. script 1.lnmp部署 #这里用的是最新测试版1.5 wget -c http://soft.vpser.net/lnmp/lnmp1.5beta.tar.gz && tar zxf lnmp1.5bet

CAD编辑器中怎么隐藏文字

CAD编辑器中怎么隐藏文字?在我们进行绘制CAD图纸的时候都会添加一些文字或者一些数字来进行标注,以此来方便我们在查看图纸的时候一目了然.但是有的时候我们标注的这样文字却挡住了CAD图纸内容,这个时候我们就需要将标注的文字进行隐藏,那CAD编辑器中怎么隐藏文字?小伙伴都想知道要怎么来操作,那下面小编就来教大家方法. 第一步:首先打开电脑,将电脑中的迅捷CAD编辑器进行打开,如果电脑中没有,可以在浏览器中搜索,点击下载安装最新版本的CAD编辑器,然后进入到CAD编辑器的使用界面就可以了. 第二步:

android ListView条目中TextView隐藏到显示时的测量

觉得ExpendableListView挺好用,但是就是代码复杂了点,我一时半会理解不了,于是就直接自己写个效果来实现.先来看一下expendableListView中展开的动画效果: 然后我模仿此效果,建立如下的item布局: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent" andr