我在ASP.Net应用程序的代码隐藏中有以下代码,其中正在读取文件,然后写入文件。
代码
var state= File.ReadAllText(Server.MapPath(string.Format("~/state/{0}", fileName)));
if(state.indexOf("1") == 0)
{
File.WriteAllText(Server.MapPath(string.Format("~/state/{0}", fileName)), newState);
}
有时,但并非总是如此,我得到以下异常。
异常
The process cannot access the file 'C:\inetpub\wwwroot\mywebsite1\state\20150905005929435_edf9267e-fad1-45a7-bfe2-0e6e643798b5' because it is being used by another process.
我猜测文件读取操作有时不会在写入操作发生之前关闭文件,或者可能是文件写入操作未在Web应用程序发出下一个请求之前关闭文件。但是,我找不到究竟是什么原因。
问题:如何避免此错误发生?使用File
类是不安全的,而是使用FileStream对象的传统方法,我总是显式地处理FileStream对象?
更新1
我尝试了一种重试循环方法,但即便如此也没有解决问题,因为如果ASP.Net页面一次又一次非常快速地提交,我就能够重现相同的错误。所以我回到了我的案例中找到一个万无一失的解决方案。
string state = null;
int i = 0;
while (i < 20) {
try {
state = File.ReadAllText(Server.MapPath(string.Format("~/state/{0}", fileName)));
} catch (Exception ex2) {
//log exception
Elmah.ErrorSignal.FromCurrentContext().Raise(ex2);
//if even retry doesn't work then throw an exception
if (i == 19) {
throw;
}
//sleep for a few milliseconds
System.Threading.Thread.Sleep(10);
}
i++;
}
i = 0;
while (i < 20) {
try {
File.WriteAllText(Server.MapPath(string.Format("~/state/{0}", fileName)), newState);
} catch (Exception ex2) {
//log exception
Elmah.ErrorSignal.FromCurrentContext().Raise(ex2);
//if even retry doesn't work then throw an exception
if (i == 19) {
throw;
}
//sleep for a few milliseconds
System.Threading.Thread.Sleep(10);
}
i++;
}
更新2
唯一可行的傻瓜式解决方案是使用文件排序方法,如usr
所示。这涉及写入不同的文件而不是刚刚读取的同一文件。要写入的文件的名称是刚刚读取的文件的名称,附加了序列号。
string fileName = hiddenField1.Value;
string state = null;
int i = 0;
while (i < 20) {
try {
state = File.ReadAllText(Server.MapPath(string.Format("~/state/{0}", fileName)));
} catch (Exception ex2) {
//log exception
Elmah.ErrorSignal.FromCurrentContext().Raise(ex2);
//if even retry doesn't work then throw an exception
if (i == 19) {
throw;
}
//sleep for a few milliseconds
System.Threading.Thread.Sleep(10);
}
i++;
}
i = 0;
while (i < 20) {
try {
//***************FILE SEQUENCING**************************
//Change the file to which state is written, so no concurrency errors happen
//between reading from and writing to same file. This is a fool-proof solution.
//Since max value of integer is more than 2 billion i.e. 2,147,483,647
//so we can be sure that our sequence will never run out of limits because an ASP.Net page
//is not going to postback 2 billion times
if (fileName.LastIndexOf("-seq_") >= 0) {
fileName = fileName.Substring(0, fileName.LastIndexOf("-seq_") + 4 + 1) + (int.Parse(fileName.Substring(fileName.LastIndexOf("-seq_") + 4 + 1)) + 1);
} else {
fileName = fileName + "-seq_1";
}
//change the file name so in next read operation the new file is read
hiddenField1.Value = fileName;
File.WriteAllText(Server.MapPath(string.Format("~/state/{0}", fileName)), newState);
} catch (Exception ex2) {
//log exception
Elmah.ErrorSignal.FromCurrentContext().Raise(ex2);
//if even retry doesn't work then throw an exception
if (i == 19) {
throw;
}
//sleep for a few milliseconds
System.Threading.Thread.Sleep(10);
}
i++;
}
上述方法的唯一缺点是,当最终用户回发到同一个ASP.Net页面时,会创建许多文件。因此,最好有一个删除过时文件的后台作业,这样可以最大限度地减少文件数量。
带序列的文件名
更新3
另一种万无一失的解决方案是在读写文件名之间进行切换。这样我们最终不会创建多个文件,只使用2个文件,因为最终用户多次回发到同一页面。代码与UPDATE 2下的代码相同,但FILE SEQUENCING
注释后的代码应替换为下面的代码。
if (fileName.LastIndexOf("-seq_1") >= 0) {
fileName = fileName.Substring(0, fileName.LastIndexOf("-seq_1"));
} else {
fileName = fileName + "-seq_1";
}
答案 0 :(得分:5)
我猜测文件读取操作有时不会在写入操作发生之前关闭文件,或者可能是文件写入操作未在Web应用程序发出下一个请求之前关闭文件。
正确。文件系统不支持原子更新。 (尤其不是在Windows上;很多怪癖。)
使用FileStream
无济于事。您只需重写File
类所具有的相同代码。 File
里面没有魔法。它只是为了您的方便而使用FileStream
包裹。
尝试保持文件不可变。当你想写一个新内容时写一个新文件。在文件名中附加序列号(例如ToString("D9")
)。阅读时选择序列号最高的文件。
或者,只需添加一个延迟较小的重试循环。
或者,使用更好的数据存储,例如数据库。文件系统真的很讨厌。例如,使用SQL Server解决这个问题很容易。
答案 1 :(得分:0)
我猜测文件读取操作有时在写操作发生之前没有关闭文件
虽然根据文档the file handle is guaranteed to be closed by this method, even if exceptions are raised,在方法返回之前不能保证关闭的时间:关闭可以异步完成。
解决此问题的一种方法是将结果写入同一目录中的临时文件,然后移动新文件以代替旧文件。