Работа с потоком временных файлов

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

}   

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


person sh0gged    schedule 05.10.2009    source источник
comment
Вам нужно использовать FileStream для этого, разве вы не можете использовать MemoryStream? Таким образом, вам не придется решать все возможные проблемы, связанные с удалением файла.   -  person armannvg    schedule 05.10.2009
comment
@armannvg, о каких проблемах ты говоришь? Это временное хранилище для очень большого файла перед его записью в базу данных.   -  person sh0gged    schedule 05.10.2009
comment
Просто обычные проблемы с удалением файлов -> IOException, UnauthorizedAccessException и т.д. Но если вы работаете с очень большим файлом, то MemoryStream не вариант   -  person armannvg    schedule 05.10.2009


Ответы (5)


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

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

person Extremeswank    schedule 28.06.2010
comment
+1, мне нравится, как много я узнаю, просто читая ответы других людей на этом сайте. - person anton.burger; 28.06.2010

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

person Robert Rossney    schedule 05.10.2009
comment
Хороший вопрос, большое спасибо. Я должен принять это во внимание. Изначально я думал о простой обертке. - person sh0gged; 06.10.2009

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

В этом решении я запускаю запрос в отдельном потоке, который заполняет 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 );
                }
            }
        }
    }
}
person Rob R    schedule 07.09.2011

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, если вам это нужно.

person Julien Lebosquain    schedule 05.10.2009

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

person Dzmitry Huba    schedule 05.10.2009