【问题标题】:Can't delete temporary files after returning FileStream返回 FileStream 后无法删除临时文件
【发布时间】:2013-04-10 18:37:40
【问题描述】:

我在 C# MVC 应用程序中有一个函数,它创建一个临时目录和一个临时文件,然后使用 FileStream 打开文件,将 FileStream 返回给调用函数,然后需要删除临时文件。但是,我不知道如何删除临时目录和文件,因为它总是错误地说“该进程无法访问该文件,因为它正在被另一个进程使用。”这是我尝试过的,但 FileStream 仍在 finally 块中使用临时文件。如何返回 FileStream 并删除临时文件?

public FileStream DownloadProjectsZipFileStream()
{
    Directory.CreateDirectory(_tempDirectory);
    // temporary file is created here
    _zipFile.Save(_tempDirectory + _tempFileName);

    try
    {
        FileStream stream = new FileStream(_tempDirectory + _tempFileName, FileMode.Open);
        return stream;
    }
    finally
    {
        File.Delete(_tempDirectory + _tempFileName);
        Directory.Delete(_tempDirectory);
    }
}

FileStream 返回的函数如下所示:

public ActionResult DownloadProjects ()
{
    ProjectDownloader projectDownloader = new ProjectDownloader();

    FileStream stream = projectDownloader.DownloadProjectsZipFileStream();
    return File(stream, "application/zip", "Projects.zip");
}

更新:我忘了说 zip 文件是 380 MB。使用 MemoryStream 时出现系统内存不足异常。

【问题讨论】:

  • FileStream 已经打开了文件,为什么还指望可以删除文件呢?这将使返回的FileStream 无法使用。
  • _zipFile 的类型是什么?
  • 你真的不需要这个文件,而是保存到内存流中。
  • 使用内存流允许我删除临时文件,但在将文件流复制到内存流时出现“系统内存不足异常”。顺便说一下,zip 文件是 380 MB。
  • @WiktorZychla 使用MemoryStream 在大文件的内存使用方面效率极低,尤其是在目标只需要处理文件而不是全部读取的情况下;如果它确实需要全部读入,则会导致双倍的内存使用。

标签: c# asp.net-mvc file-io filestream temporary-files


【解决方案1】:

这是我使用的上述内容的精简版:

public class DeleteAfterReadingStream : FileStream
{
    public DeleteAfterReadingStream(string path)
        : base(path, FileMode.Open)
    {
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        if (File.Exists(Name))
            File.Delete(Name);
    }
}

【讨论】:

    【解决方案2】:

    您可以创建一个实现 Stream 合同并在内部包含 FileStream 的包装器类,并维护文件的路径。

    所有标准的Stream 方法和属性都将被传递给FileStream 实例。

    当这个包装类是Disposed 时,您将(在Disposeing 包装的FileStream 之后)然后删除该文件。

    【讨论】:

    • 这就是我最终要做的。我只是扩展了 FileStream 并在构造函数中传递了临时文件/目录路径,并在 Dispose 方法中添加了临时文件和目录的删除代码。感谢您的建议!
    【解决方案3】:

    我使用了 Damien_The_Unbeliever(已接受的答案)的建议,写了出来,效果很好。只是想我会分享课程:

    public class BurnAfterReadingFileStream : Stream
    {
        private FileStream fs;
    
        public BurnAfterReadingFileStream(string path) { fs = System.IO.File.OpenRead(path); }
    
        public override bool CanRead { get { return fs.CanRead; } }
    
        public override bool CanSeek { get { return fs.CanRead; } }
    
        public override bool CanWrite { get { return fs.CanRead; } }
    
        public override void Flush() { fs.Flush(); }
    
        public override long Length { get { return fs.Length; } }
    
        public override long Position { get { return fs.Position; } set { fs.Position = value; } }
    
        public override int Read(byte[] buffer, int offset, int count) { return fs.Read(buffer, offset, count); }
    
        public override long Seek(long offset, SeekOrigin origin) { return fs.Seek(offset, origin); }
    
        public override void SetLength(long value) { fs.SetLength(value); }
    
        public override void Write(byte[] buffer, int offset, int count) { fs.Write(buffer, offset, count); }
    
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            if (Position > 0) //web service quickly disposes the object (with the position at 0), but it must get rebuilt and re-disposed when the client reads it (when the position is not zero)
            {
                fs.Close();
                if (System.IO.File.Exists(fs.Name))
                    try { System.IO.File.Delete(fs.Name); }
                    finally { }
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      问题是文件被写入响应后才能删除,文件只有在动作返回后才由FileStreamResult写入。

      一种处理方法是创建一个 FileResult 的子类来删除文件。

      子类化FilePathResult 更容易,这样类就可以访问文件名。

      public class FilePathWithDeleteResult : FilePathResult
      {
          public FilePathResult(string fileName, string contentType)
              : base(string fileName, string contentType)
          {
          }
      
          protected override void WriteFile(HttpResponseBase response)
          {
              base.WriteFile(response);
              File.Delete(FileName);
              Directory.Delete(FileName);
          }
      }
      

      注意:我没有测试过上述内容。在使用之前删除它的所有错误。

      现在将控制器代码更改为:

      public ActionResult DownloadProjects ()
      {
          Directory.CreateDirectory(_tempDirectory);
          // temporary file is created here
          _zipFile.Save(_tempDirectory + _tempFileName);
      
          return new FilePathWithDeleteResult(_tempDirectory + _tempFileName, "application/zip") { FileDownloadName = "Projects.zip" };
      }
      

      【讨论】:

      • 不幸的是,这不起作用。它给出了“句柄无效”异常。
      【解决方案5】:

      我使用@hwiechers 建议的方法,但使其工作的唯一方法是在删除文件之前关闭响应流。

      这里是源代码,注意我在删除流之前刷新了它。

      public class FilePathAutoDeleteResult : FilePathResult
      {
          public FilePathAutoDeleteResult(string fileName, string contentType) : base(fileName, contentType)
          {
          }
      
          protected override void WriteFile(HttpResponseBase response)
          {
              base.WriteFile(response);
              response.Flush();
              File.Delete(FileName);
          }
      
      }
      

      这是控制器应该如何调用它:

      public ActionResult DownloadFile() {
      
          var tempFile = Path.GetTempFileName();
      
          //do your file processing here...
          //For example: generate a pdf file
      
          return new FilePathAutoDeleteResult(tempFile, "application/pdf")
          {
              FileDownloadName = "Awesome pdf file.pdf"
          };
      }
      

      【讨论】:

      • 太棒了。 response.Flush() 使@hwiechers 解决方案确实有效。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多