使用临时文件流进行处理

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

class TempFileStream: FileStream { string m_TempFileName = Path.GetTempFileName(); public TempFileStream(FileMode fileMode): base(m_TempFileName,fileMode) {} /// ... public ovverride Dispose(bool disposing) { /// ??? } } 

我该如何简单安全地实现这一点?

试试这个:

 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方法,因为它全部都由你照顾。

这是一个有趣的想法,但是这个设计让我感到困扰。 如果您已经在设计中解决了这个问题,请原谅我。 但是如果你的设计只是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的方法,则第二种方法将抛出exception。 (或者设置一个属性,其set访问器引发一个事件,其事件处理程序将调用一个写入TempStream的方法,这个问题可能最终会发生。)第一个只会产生意外的结果,没有明显的原因。

我认为,你可以通过使用FileAccess.Write让你的TempFileStream类打开底层的FileStream来解决这个问题。 然后实现一个关闭此FileStreamRewind方法,并创建一个使用FileAccess.Read的新方法。 如果你这样做,任何试图在文件打开以进行读访问时写入(或反之亦然)的方法至少会抛出exception。

我知道这是一个较老的线程,但这是一个替代解决方案。 我开始实现TempFileStream,但我想要更多的并发性。 我的用例涉及通过MVC将[可能的MB]数据库结果导出到CSV文件。 我希望在数据库查询中提供数据后立即开始下载到客户端,而不是等待在开始下载之前写入可能很大的临时文件。

在此解决方案中,我在一个填充AnonymousPipeStream的单独线程中启动查询。 然后,主线程可以将数据从管道的另一端汲取为可用的数据。 它使用.Net 4任务。

希望别人觉得这很有用。

-抢

控制器方法:

 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 ); } } } } } 
 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添加适当的exception处理。

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