Обработка с временным потоком файлов
Скажем, я хочу определить класс 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 ответов
Попробуйте это вместо этого:
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 гарантирует, что ОС автоматически удалит временный файл при его закрытии. Нет необходимости в специальном методе утилизации, потому что он позаботится о вас.
Это интересная идея, но в этом дизайне есть кое-что, что меня беспокоит. Простите, если вы уже рассмотрели это в своем дизайне. Но если ваш дизайн просто обертка вокруг 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
, (Или устанавливает свойство, метод доступа которого вызывает событие, чей обработчик события отправляет вызов методу, который записывает в TempStream
, что, как эта проблема, вероятно, в конечном итоге происходит.) Первый просто даст неожиданные результаты без видимой причины.
Вы можете обойти это, я думаю, имея TempFileStream
класс открыть базовый FileStream
с помощью FileAccess.Write
, Затем реализовать Rewind
метод, который закрывает это FileStream
и создает новый, который использует FileAccess.Read
, Если вы сделаете это, любой метод, который попытается записать в файл, пока он открыт для чтения (или наоборот), по крайней мере, сгенерирует исключение.
Я знаю, что это более старая тема, но вот альтернативное решение. Я начал реализовывать 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 );
}
}
}
}
}
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, если вам нужно.
В основном, согласно логике TempFileStream, вы всегда используете только что созданный файл с уникальным именем (это то, что делает Path.GetTempFileName), и вы всегда удаляете его после его использования. Поэтому нет необходимости предоставлять конструктор, который принимает FileMode, поскольку вы всегда используете его в одном и том же режиме.