Thursday, April 8

不管你是否相信,BUG永远与我们同在。包括使用JAVA的"勇气"与"机遇"号火星探测器。
今天遇到的问题是来自.NET Framework 1.0/1.1的FileStream。

现象:
当我将一些文本内容写到软盘里的一个文件的时候,出现了IOException,磁盘容量已满。我的catch部分捕获了这个异常,但是我无论如何无法关闭已经打开的文件。结果就是在软盘里生成了一个0字节的文件,在我的程序不完全关闭的情况下,我用文件管理器无法删除它。

原因:
我发现这个异常是由Close引起的,Close方法在内部调用了Flush方法将缓冲区里的内容写到软盘里去的时候,发生了这个异常。异常发生以后,FileStream没有关闭文件句柄(HANDLE),导致文件一直被占用。即使我试图用CloseHandle来关闭文件句柄也不成功,原因是取得Handle这个属性在内部会调用Flush方法,导致异常发生。

解决办法:
1,不用.NET Framework 1.0/1.1提供的FileStream,自己封装Win32 API (太不现实了吧:s)
2,在write之前,先调用SetLength。SetLength这个方法会改变stream的大小,你需要预先计算一下你准备写多少字节到文件里去。如果磁盘已满,在这个方法上就会发生异常,这时候内部缓冲区里还没有内容,所以Close会正确关闭。
3,Close之前清空内部缓冲区。FileStream的设计者为了保证数据都会被写入文件,在这个类的很多地方都调用Flush来将内部缓冲区的内容写到磁盘上,而没有给用户提供直接操作这一缓冲区的方法。
只好用一些黑客手段达到目的了。
...
catch( IOException excep )
{
// 清空内部缓冲区
Type streamType = stream.GetType();
System.Reflection.FieldInfo field;
// 得到private int _writePos 的FieldInfo
field = streamType.GetField( "_writePos",BindingFlags.NonPublic | BindingFlags.Instance );
// 设置缓冲区指针的位置为0
field.SetValue( stream, (int)0 );
}
finally
{
stream.Close(); // Close 成功。
}

总结:
方法3无法保证对.NET Framework 今后的版本也有效。方法2对于很多情况不是很有效。方法1需要高超的技术水平。方法0,期待MS改进它的Framework 。

参考:
1,http://mag.autumn.org/Content.aspx?id=20040202140342(日文)
2,http://www.gdncom.jp/general/mllog/tech/techDetail.aspx?ID=215(日文)

No comments: