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

Sun*_*nil 4 c# asp.net file-io

我在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);
}
Run Code Online (Sandbox Code Playgroud)

有时候,但并非总是如此,我得到以下异常.

例外

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++;
  }
Run Code Online (Sandbox Code Playgroud)

更新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++;
  }
Run Code Online (Sandbox Code Playgroud)

上述方法的唯一缺点是,当最终用户回发到同一个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";
        }
Run Code Online (Sandbox Code Playgroud)

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

usr*_*usr 5

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

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

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

尝试保持文件不可变.当你想写一个新内容时写一个新文件.在文件名中附加序列号(例如ToString("D9")).读取时选择序列号最高的文件.

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

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