Обработка с временным потоком файлов - PullRequest
6 голосов
/ 05 октября 2009

Скажем, я хочу определить класс 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)
 {
   /// ???
 }

}   

Как мне реализовать это просто и безопасно?

Ответы [ 5 ]

26 голосов
/ 28 июня 2010

Попробуйте вместо этого:

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, потому что он обо всем позаботится о вас.

5 голосов
/ 05 октября 2009

Это интересная идея, но в этом дизайне есть кое-что, что меня беспокоит. Простите, если вы уже рассмотрели это в своем дизайне. Но если ваш дизайн представляет собой простую обертку вокруг FileStream, есть небольшая, но, я думаю, существенная проблема.

Если вы удаляете файл при закрытии потока, это означает, что единственный способ использовать данные в файле - это если FileAccess равен ReadWrite. Правильный? Другими словами, вы будете использовать файл с кодом, который выглядит следующим образом:

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, что, вероятно, приведет к возникновению этой проблемы.) Первый из них просто выдаст неожиданные результаты без видимой причины. .

Думаю, вы можете обойти это, если ваш класс TempFileStream откроет базовый FileStream с помощью FileAccess.Write. Затем реализуйте метод Rewind, который закрывает этот FileStream и создает новый, который использует FileAccess.Read. Если вы сделаете это, любой метод, который попытается записать в файл, пока он открыт для чтения (или наоборот), по крайней мере, выдаст исключение.

3 голосов
/ 07 сентября 2011

Я знаю, что это более старая тема, но вот альтернативное решение. Я начал реализовывать TempFileStream, но хотел большего параллелизма. Мой вариант использования включает в себя экспорт [потенциально МБ] результатов базы данных в файл CSV через MVC. Я хочу начать загрузку на клиент, как только будут получены данные из запроса к базе данных, а не ждать, пока записать потенциально большой временный файл, прежде чем я начну загрузку.

В этом решении я запускаю запрос в отдельном потоке, который заполняет AnonymousPipeStream. Затем главный поток может отбросить данные с другого конца канала, как только они станут доступными. Использует .Net 4 Tasks.

Надеюсь, кто-то еще найдет это полезным.

1007 * Роб *

Метод контроллера:

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 голосов
/ 05 октября 2009
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 голосов
/ 05 октября 2009

В основном, согласно логике TempFileStream, вы всегда используете только что созданный файл с уникальным именем (именно так делает Path.GetTempFileName) и всегда удаляете его после его использования. Поэтому нет необходимости предоставлять конструктор, который принимает FileMode, поскольку вы всегда используете его в одном и том же режиме.

...