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

时间:2015-09-05 13:32:45

标签: 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);
}

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

异常

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页面时,会创建许多文件。因此,最好有一个删除过时文件的后台作业,这样可以最大限度地减少文件数量。

带序列的文件名

File Sequencing Naming

更新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";
        }

采用替代方式的文件名 File Alternating Approach

2 个答案:

答案 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,在方法返回之前不能保证关闭的时间:关闭可以异步完成。

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