处理临时文件流

7

假设我想定义一个TempFileStream类,使用Path.GetTempFileName()方法创建临时文件。当TempFileStream的对象不再需要(例如关闭或释放)时,必须删除临时文件:

class TempFileStream: FileStream
{
  string m_TempFileName = Path.GetTempFileName();
  public TempFileStream(FileMode fileMode): base(m_TempFileName,fileMode) {}

  /// ...

 public ovverride Dispose(bool disposing)
 {
   /// ???
 }

}   

我该如何简单而安全地实现这个功能呢?

你必须使用FileStream吗?难道不能使用MemoryStream吗?这样你就不必处理与删除文件相关的所有可能问题。 - armannvg
@armannvg,你在谈论什么问题?这是一个非常大的文件在被记录到数据库之前的临时存储。 - sh0gged
通常的文件删除问题,如IOException、UnauthorizedAccessException等。但如果您正在处理非常大的文件,则MemoryStream不是一个选项。 - armannvg
5个回答

29

试试这个:

public class TempFileStream : FileStream
{
    public TempFileStream()
        : base(Path.GetTempFileName(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose) { }
    public TempFileStream(FileAccess access)
        : base(Path.GetTempFileName(), FileMode.Create, access, FileShare.Read, 4096, FileOptions.DeleteOnClose) { }
    public TempFileStream(FileAccess access, FileShare share)
        : base(Path.GetTempFileName(), FileMode.Create, access, share, 4096, FileOptions.DeleteOnClose) { }
    public TempFileStream(FileAccess access, FileShare share, int bufferSize)
        : base(Path.GetTempFileName(), FileMode.Create, access, share, bufferSize, FileOptions.DeleteOnClose) { }
}

使用FileOptions.DeleteOnClose选项可以确保操作系统自动在关闭文件时删除临时文件。无需特殊的Dispose方法,因为这一切都已经为您处理好了。


3
+1,我喜欢在这个网站上阅读他人的回答并从中学到很多东西。 - anton.burger

5
这是一个有趣的想法,但这个设计中有一些问题让我感到困扰。如果您在设计中已经解决了这个问题,那么请原谅我。但是,如果您的设计只是FileStream的简单封装,那么会出现一个微妙但我认为很重要的问题。
如果在关闭流时删除文件,那么实际上使用文件中的数据的唯一方式就是FileAccessReadWrite。也就是说,您将使用类似以下代码的代码来使用该文件:
using (TempFileStream t as new TempFileStream())
{
   WriteDataToTempFile(t);
   t.Seek(0, SeekOrigin.Begin);
   ReadDataFromTempFile(t);
}

我看到的问题是ReadDataFromTempFile期望以只读方式打开文件,而不是读写方式。这会导致一些很难发现的错误。考虑以下代码:
using (TempFileStream t as new TempFileStream())
{
   MyClass o = new MyClass(o);
   o.TempStream = t;
   o.ProduceOutput();
   t.Seek(0, SeekOrigin.Begin);
   o.ProcessOutput();
}

...与此相比:

MyClass o = new MyClass();
string n = Path.GetTempFileName();
using (FileStream s = new FileStream(n, FileMode.Create, FileAccess.Write))
{
   o.TempStream = t;
   o.ProduceOutput();
}
using (FileStream s = new FileStream(n, FileMode.Open, FileAccess.Read))
{
   o.TempStream = t;
   o.ProcessOutput();
}
File.Delete(n);

当然,第一种方法比第二种方法更短。但是如果 ProcessOutput 调用一个写入 TempStream 的方法,第二种方法将抛出异常。(或设置其 set 访问器引发调用一个写入 TempStream 的方法的事件处理程序,这可能是这个问题最终发生的方式) 第一种方法只会产生无法预料的结果,没有明显的原因。
我认为你可以通过使用 FileAccess.Write 打开基础的 FileStream,然后实现一个 Rewind 方法来解决这个问题。该方法将关闭这个 FileStream 并创建一个使用 FileAccess.Read 的新文件流。如果你这样做了,在以读访问方式打开文件时(或反之),任何试图写入该文件的方法都至少会抛出异常。

好观点,非常感谢。我应该考虑到这一点。最初我只考虑了一个简单的包装器。 - sh0gged

3

我知道这是一个旧的线程,但这里有一个备选方案。我开始实现TempFileStream,但我想要更多的并发性。我的用例涉及通过MVC将[潜在的MB级]数据库结果导出到CSV文件。我希望在从数据库查询获得数据可用时立即开始向客户端下载,而不是等待可能很大的临时文件被写入后再开始下载。

在这个解决方案中,我在单独的线程中启动查询,该线程填充AnonymousPipeStream。主线程可以随时从管道的另一端吞咽数据。它使用了.Net 4任务。

希望其他人也会发现这个有用。

-Rob

控制器方法:

public FileResult ResultExport ( ReportOptions options )
{
    ResultExport rpt = new ResultExport( options );
    HttpContext.Response.BufferOutput = false;
    return File( rpt.Export(), "text/csv", "results.csv" );
}

模型:

public ResultExport
{
    private AnonymousPipeServerStream WriteStream = null;

    public Stream Export()
    {
        //
        // We'll fire off the database query in a background
        // thread.  It will write data to one end of the pipe.  We'll return the reader
        // end of that pipe to our caller.
        //
        WriteStream = new AnonymousPipeServerStream( PipeDirection.Out );
        AnonymousPipeClientStream reader = new AnonymousPipeClientStream( PipeDirection.In, WriteStream.ClientSafePipeHandle );

        //
        // Call Execute() in a background thread.
        //
        Task.Factory.StartNew( () => Execute() );

        //
        // While Execute() is filling the pipe with data,
        // return the reader end of the pipe to our caller.
        //
        return reader;
    }

    private void Execute ()
    {
        //
        // WriteStream should only by populated by Export()
        //
        if( WriteStream != null )
        {
            using ( StreamWriter sw = new StreamWriter( WriteStream, Encoding.UTF8, 4096 ) )
            {
                //
                // Shove data into the StreamWriter as we get it from the database
                //
                foreach ( string line in ExportCore() )
                {
                    // Each line is a comma-delimited set of values
                    sw.WriteLine( line );
                }
            }
        }
    }
}

2
base.Dispose(disposing); // disposes the base stream so the file is no longer used
if (disposing)
    File.Delete(m_TempFileName); // deletes the file

如果需要,您应该为File.Delete添加适当的异常处理。


0

基本上根据TempFileStream的逻辑,您始终使用具有唯一名称的刚创建的文件(这就是Path.GetTempFileName的作用),并且在使用后始终将其删除。因此,无需提供接受FileMode的构造函数,因为您始终以相同的模式使用它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接