c# - pueden - Manejo con secuencia de archivos temporales
se pueden borrar los archivos temporales (5)
Digamos que quiero definir una clase TempFileStream que crea un archivo temporal usando el método Path.GetTempFileName (). Se debe eliminar un archivo temporal cuando ya no se necesita el objeto de TempFileStream, por ejemplo, cerrado o desechado:
class TempFileStream: FileStream
{
string m_TempFileName = Path.GetTempFileName();
public TempFileStream(FileMode fileMode): base(m_TempFileName,fileMode) {}
/// ...
public ovverride Dispose(bool disposing)
{
/// ???
}
}
¿Cómo debo implementar esto de manera sencilla y segura?
Básicamente, según la lógica de TempFileStream, siempre utiliza el archivo recién creado con un nombre único (eso es lo que hace Path.GetTempFileName) y siempre lo elimina después de su uso. Por lo tanto, no es necesario proporcionar un constructor que acepte FileMode ya que siempre lo usa en el mismo modo.
Esta es una idea interesante, pero hay algo en este diseño que me preocupa. Perdóname si ya has abordado esto en tu diseño. Pero si su diseño es simplemente un simple contenedor de FileStream
, hay un problema sutil pero significativo.
Si está eliminando el archivo cuando se cierra la secuencia, eso significa que la única manera de utilizar realmente los datos en el archivo es si FileAccess
es ReadWrite
. ¿Correcto? En otras palabras, usarás el archivo con un código que se ve así:
using (TempFileStream t as new TempFileStream())
{
WriteDataToTempFile(t);
t.Seek(0, SeekOrigin.Begin);
ReadDataFromTempFile(t);
}
El problema que veo es que ReadDataFromTempFile
espera que el archivo se abra para acceso de lectura, no de lectura / escritura. Y esto abre la puerta a algunos errores que creo que serán muy difíciles de encontrar. Considere un código como este:
using (TempFileStream t as new TempFileStream())
{
MyClass o = new MyClass(o);
o.TempStream = t;
o.ProduceOutput();
t.Seek(0, SeekOrigin.Begin);
o.ProcessOutput();
}
... cuando se compara con esto:
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);
Claro, el primer método es más corto que el segundo. Pero el segundo método lanzará una excepción si ProcessOutput
llama a un método que escribe en TempStream
. (O establece una propiedad cuyo acceso de conjunto provoca un evento cuyo controlador de eventos distribuye una llamada a un método que escribe en TempStream
, que explica cómo este problema probablemente terminará sucediendo). El primero solo producirá resultados inesperados sin motivo aparente.
Puede evitar esto, creo, haciendo que su clase TempFileStream
abra el FileStream
subyacente utilizando FileAccess.Write
. Luego, implemente un método Rewind
que cierre este FileStream
y cree uno nuevo que use FileAccess.Read
. Si lo hace, cualquier método que intente escribir en el archivo mientras está abierto para acceso de lectura (o viceversa) arrojará al menos una excepción.
base.Dispose(disposing); // disposes the base stream so the file is no longer used
if (disposing)
File.Delete(m_TempFileName); // deletes the file
Debe agregar el manejo de excepciones adecuado para File.Delete si es necesario.
Prueba este en su lugar:
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) { }
}
La opción FileOptions.DeleteOnClose asegurará que el sistema operativo elimine el archivo temporal automáticamente al cerrar el archivo. No es necesario un método especial de eliminación, ya que todo se solucionó por usted.
Sé que este es un hilo más antiguo, pero aquí hay una solución alternativa. Empecé a implementar TempFileStream, pero quería más concurrencia. Mi caso de uso implica exportar [potencialmente MBs] de resultados de bases de datos a un archivo CSV a través de MVC. Quiero comenzar a descargar al cliente tan pronto como haya datos disponibles a partir de la consulta de la base de datos, en lugar de esperar a que se escriba un archivo temporal potencialmente grande antes de comenzar la descarga.
En esta solución, inicio la consulta en un hilo separado que llena un AnonymousPipeStream. El hilo principal puede sorber los datos del otro extremo de la tubería como está disponible. Utiliza .Net 4 tareas.
Espero que alguien más encuentre esto útil.
-Robar
Método del controlador:
public FileResult ResultExport ( ReportOptions options )
{
ResultExport rpt = new ResultExport( options );
HttpContext.Response.BufferOutput = false;
return File( rpt.Export(), "text/csv", "results.csv" );
}
Modelo:
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 );
}
}
}
}
}