2010-05-12 16 views
14

Tengo que transferir archivos grandes entre computadoras a través de conexiones no confiables usando WCF.Memorystream y Large Object Heap

Como deseo poder reanudar el archivo y no quiero estar limitado en mi tamaño de archivo por WCF, estoy fragmentando los archivos en 1MB. Estos "trozos" se transportan como una secuencia. Lo cual funciona bastante bien, hasta ahora.

Mis pasos son:

  1. filestream abierta
  2. lectura trozo del archivo en bytes [] y crear MemoryStream
  3. trozo de transferencia de
  4. de nuevo a 2. hasta que todo el archivo se envía

Mi problema está en el paso 2. Supongo que cuando creo una secuencia de memoria desde una matriz de bytes, terminará en LOH y ultima tely causa una excepción fuera de la memoria. Realmente no pude crear este error, tal vez estoy equivocado en mi suposición.

Ahora, no quiero enviar el byte [] en el mensaje, ya que WCF me dirá que el tamaño de la matriz es demasiado grande. Puedo cambiar el tamaño de matriz máximo permitido y/o el tamaño de mi fragmento, pero espero que haya otra solución.

Mi pregunta real (s):

  • ¿Mi solución actual crear objetos en la LOH y tendrá que me causa problema?
  • ¿Hay una mejor manera de resolver esto?

Por cierto: en el lado de la recepción, simplemente leo trozos más pequeños de la secuencia de llegada y los escribo directamente en el archivo, por lo que no involucran arrays de bytes grandes.

Editar:

solución actual:

for (int i = resumeChunk; i < chunks; i++) 
{ 
byte[] buffer = new byte[chunkSize]; 
fileStream.Position = i * chunkSize; 
int actualLength = fileStream.Read(buffer, 0, (int)chunkSize); 
Array.Resize(ref buffer, actualLength); 
using (MemoryStream stream = new MemoryStream(buffer)) 
{ 
    UploadFile(stream); 
} 
} 

Respuesta

35

Espero que esto esté bien. Es mi primera respuesta en StackOverflow.

Sí, absolutamente si su tamaño de conjunto supera los 85000 bytes, la matriz se asignará en el montón de objetos grandes. Probablemente no se quede sin memoria muy rápidamente ya que está asignando y desasignando áreas contiguas de memoria que son del mismo tamaño, por lo que cuando la memoria se llena, el tiempo de ejecución puede acomodar un nuevo fragmento en un área de memoria recuperada anterior.

Me preocupa un poco la llamada Array.Resize ya que creará otra matriz (vea http://msdn.microsoft.com/en-us/library/1ffy6686(VS.80).aspx). Este es un paso innecesario si actualLength == Chunksize como lo será para todo menos el último fragmento.Así que como sugerencia mínima:

if (actualLength != chunkSize) Array.Resize(ref buffer, actualLength); 

Esto debería eliminar una gran cantidad de asignaciones. Si el tamaño real no es el mismo que el tamaño de fragmento, pero sigue siendo> 85000, el nuevo conjunto también se asignará en el montón de objetos grandes, lo que podría hacer que se fragmente y posiblemente ocasione pérdidas de memoria aparentes. Creo que aún tardaré mucho tiempo en quedarme sin memoria, ya que la fuga sería bastante lenta.

Creo que una mejor implementación sería usar algún tipo de agrupación de almacenamiento intermedio para proporcionar las matrices. Podrías tirar el tuyo (sería demasiado complicado) pero WCF te proporciona uno. He reescrito el código ligeramente para tener advatage de que:

BufferManager bm = BufferManager.CreateBufferManager(chunkSize * 10, chunkSize); 

for (int i = resumeChunk; i < chunks; i++) 
{ 
    byte[] buffer = bm.TakeBuffer(chunkSize); 
    try 
    { 
     fileStream.Position = i * chunkSize; 
     int actualLength = fileStream.Read(buffer, 0, (int)chunkSize); 
     if (actualLength == 0) break; 
     //Array.Resize(ref buffer, actualLength); 
     using (MemoryStream stream = new MemoryStream(buffer)) 
     { 
      UploadFile(stream, actualLength); 
     } 
    } 
    finally 
    { 
     bm.ReturnBuffer(buffer); 
    } 
} 

esto supone que la implementación de UploadFile puede ser reescrita para tomar un int para el no. de bytes para escribir.

espero que esto ayude a

Joe

+1

¡Muchas gracias! Esta es en verdad una respuesta brillante. Gracias por señalar el problema Array.Resize. Tampoco había oído hablar de BufferManager, parece que esto también me ayudará mucho en otras áreas. Esto es mucho más de lo que esperaba, así que pensé en comenzar una pequeña recompensa y dártela, pero tengo que esperar 23 horas después de comenzar una recompensa ...Entonces tienes que esperar también :) – flayn

+0

Gracias por eso. Estoy muy contento de poder ser de ayuda. Avísame si hay algo más. Mirando hacia atrás, podría valer la pena señalar que la implementación óptima compartiría una única instancia de BufferManager en todo el servicio. No sé qué tan práctico sería para ti. –

+0

+1 Acabo de tropezar con este buscando una respuesta a un problema similar. Nunca escuché sobre BufferManager antes - ¡increíble! Supongo que esto será algo para recordar en el futuro. –

2

No estoy tan seguro de la primera parte de su pregunta, sino como de una mejor manera - ¿ha considerado BITS? Permite la descarga en segundo plano de archivos a través de http. Puede proporcionarle un http: // o file: // URI. Se puede reanudar desde el punto en que se interrumpió y se descarga en trozos de bytes utilizando el método RANGE en la HEADER de http. Lo usa Windows Update. Puede suscribirse a eventos que brinden información sobre el progreso y la finalización.

+0

Gracias por su sugerencia, pero no quiero instalar IIS en cada máquina. – flayn

+2

No hay problema, solo un pensamiento. Solo para señalar que solo necesita IIS en cada máquina si están cargando. Si los clientes solo están descargando usando BITS, entonces no requieren IIS. –

1

he llegado con otra solución para esto, que me haga saber lo que piensas!

Como no quiero tener grandes cantidades de datos en la memoria, estaba buscando una manera elegante de almacenar temporalmente conjuntos de bytes o una secuencia.

La idea es crear un archivo temporal (no necesita derechos específicos para hacerlo) y luego usarlo de forma similar a una secuencia de memoria. Hacer que la clase Desechable limpiará el archivo temporal una vez que se haya utilizado.

public class TempFileStream : Stream 
{ 
    private readonly string _filename; 
    private readonly FileStream _fileStream; 

    public TempFileStream() 
    { 
    this._filename = Path.GetTempFileName(); 
    this._fileStream = File.Open(this._filename, FileMode.OpenOrCreate, FileAccess.ReadWrite); 
    } 

    public override bool CanRead 
    { 
    get 
    { 
    return this._fileStream.CanRead; 
    } 
    } 

// and so on with wrapping the stream to the underlying filestream 

...

// finally overrride the Dispose Method and remove the temp file  
protected override void Dispose(bool disposing) 
    { 
     base.Dispose(disposing); 

    if (disposing) 
    { 
    this._fileStream.Close(); 
    this._fileStream.Dispose(); 

    try 
    { 
     File.Delete(this._filename); 
    } 
    catch (Exception) 
    { 
    // if something goes wrong while deleting the temp file we can ignore it. 
    } 
    } 
2

Ver también RecyclableMemoryStream. De this article:

Microsoft.IO.RecyclableMemoryStream es un reemplazo MemoryStream que ofrece un comportamiento superior para sistemas de rendimiento crítico. En particular, se ha optimizado para hacer lo siguiente:

  • Eliminar las grandes asignaciones del montón de objetos mediante el uso de tampones agrupados
  • incurrir muchos menos Gen 2 GC, y pasan mucho menos tiempo en pausa debido a la GC
  • pérdidas de memoria
  • Evita por tener un tamaño de grupo acotado
  • Evitar la fragmentación de memoria
  • proporcionan una excelente debuggability
  • Proporcionar métricas para el seguimiento del rendimiento