Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling with temporary file stream

Say I want to define a TempFileStream class that creates a temporary file using Path.GetTempFileName() method. A temporary file must be deleted when TempFileStream's object is no longer needed, e.g. closed or disposed:

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

  /// ...

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

}   

How should I implement this simply and safely?

like image 487
sh0gged Avatar asked Oct 05 '09 11:10

sh0gged


3 Answers

Try this one instead:

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) { }
}

The FileOptions.DeleteOnClose option will ensure that the OS deletes the temporary file automatically when you close out the file. No need for a special Dispose method, because it's all taken care of for you.

like image 143
Extremeswank Avatar answered Oct 21 '22 20:10

Extremeswank


This is an interesting idea, but there's something about this design that troubles me. Forgive me if you've already addressed this in your design. But if your design is just a simple wrapper around FileStream, there's a subtle but, I think, significant problem.

If you're deleting the file when the stream is closed, that means that the only way to actually use the data in the file is if the FileAccess is ReadWrite. Correct? In other words, you'll be using the file with code that looks like this:

using (TempFileStream t as new TempFileStream())
{
   WriteDataToTempFile(t);
   t.Seek(0, SeekOrigin.Begin);
   ReadDataFromTempFile(t);
}

The problem I see is that ReadDataFromTempFile is expecting the file to be opened for read access, not read/write access. And this opens the door for some bugs that I think will be very hard to find. Consider code like this:

using (TempFileStream t as new TempFileStream())
{
   MyClass o = new MyClass(o);
   o.TempStream = t;
   o.ProduceOutput();
   t.Seek(0, SeekOrigin.Begin);
   o.ProcessOutput();
}

...when compared with this:

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);

Sure, the first method is shorter than the second. But the second method will throw an exception if ProcessOutput calls a method that writes to TempStream. (Or sets a property whose set accessor raises an event whose event handler dispatches a call to a method that writes to TempStream, which how this problem will probably end up happening.) The first one will just produce unexpected results for no apparent reason.

You can get around this, I think, by having your TempFileStream class open the underlying FileStream using FileAccess.Write. Then implement a Rewind method that closes this FileStream and creates a new one that uses FileAccess.Read. If you do that, any method that tries to write to the file while it's opened for read access (or vice versa) will at least throw an exception.

like image 30
Robert Rossney Avatar answered Oct 21 '22 19:10

Robert Rossney


I know this is an older thread, but here's an alternate solution. I started to implement the TempFileStream, but I wanted more concurrency. My use-case involves exporting [potentially MBs of] database results to a CSV file via MVC. I want to begin downloading to the client as soon as data are available from the database query rather than wait for a potentially large temp file to be written before I start downloading.

In this solution, I launch the query in a separate thread which fills an AnonymousPipeStream. The main thread can then slurp the data from the other end of the pipe as its available. It uses .Net 4 Tasks.

Hope someone else finds this useful.

-Rob

Controller method:

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

Model:

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 );
                }
            }
        }
    }
}
like image 3
Rob R Avatar answered Oct 21 '22 19:10

Rob R