在C#中对File类使用静态方法是否安全?

我在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); } 

有时,但并非总是如此,我得到以下exception。

例外

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 = 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"; } 

具有交替方法的文件名 文件交替方法

我猜测文件读取操作有时不会在写入操作发生之前关闭文件,或者可能是文件写入操作未在Web应用程序的下一个请求到来之前关闭文件。

正确。 文件系统不支持primefaces更新。 (尤其不是在Windows上;很多怪癖。)

使用FileStream没有帮助。 您只需重写与File类相同的代码。 File里面没有魔法。 它只是为了您的方便而使用FileStream包装。

尝试保持文件不可变。 当您要编写新内容时,请编写新文件。 在文件名中附加序列号(例如ToString("D9") )。 阅读时选择序列号最高的文件。

或者,只需添加一个延迟较小的重试循环。

或者,使用更好的数据存储,例如数据库。 文件系统真的很讨厌。 例如,使用SQL Server解决这个问题很容易。

我猜测文件读取操作有时不会在写操作发生之前关闭文件

虽然根据文档,该方法保证文件句柄被关闭,但即使引发exception ,也不能保证在方法返回之前发生关闭的时间:关闭可以异步完成。

解决此问题的一种方法是将结果写入同一目录中的临时文件,然后移动新文件以代替旧文件。