Memorystream e heap di oggetti di grandi dimensioni

Devo trasferire file di grandi dimensioni tra computer su connessioni inaffidabili usando WCF.

Perché voglio essere in grado di riprendere il file e non voglio essere limitato nel mio file di WCF, sto tagliando i file in pezzi da 1 MB. Questi “pezzi” vengono trasportati come stream. Che funziona abbastanza bene, finora.

I miei passi sono:

  1. aprire il filestream
  2. leggi il pezzo dal file in byte [] e crea il memorystream
  3. trasferimento pezzo
  4. torna a 2. fino a quando non viene inviato l’intero file

Il mio problema è nel passo 2. Presumo che quando creo un stream di memoria da un array di byte, finirà sul LOH e alla fine causerà un’eccezione outofmemory. Non sono riuscito a creare questo errore, forse mi sbaglio nella mia ipotesi.

Ora, non voglio inviare il byte [] nel messaggio, in quanto WCF mi dirà che la dimensione dell’array è troppo grande. Posso modificare la dimensione massima dell’array consentito e / o la dimensione del mio blocco, ma spero che ci sia un’altra soluzione.

La mia domanda reale (s):

  • La mia attuale soluzione creerà oggetti sul LOH e questo mi causerà problemi?
  • C’è un modo migliore per risolvere questo?

A proposito: dal lato ricevente, leggo semplici pezzi più piccoli dal stream in arrivo e li scrivo direttamente nel file, quindi non sono coinvolti array di byte di grandi dimensioni.

Modificare:

soluzione corrente:

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); } } 

Spero che vada bene. È la mia prima risposta su StackOverflow.

Sì, assolutamente se il tuo chunksize supera gli 85000 byte, allora l’array verrà allocato sull’heap di oggetti di grandi dimensioni. Probabilmente non esaurirai la memoria molto rapidamente mentre stai allocando e deallocando aree contigue di memoria che sono tutte della stessa dimensione, quindi quando la memoria riempie il runtime puoi inserire un nuovo blocco in una vecchia area di memoria recuperata.

Sarei un po ‘preoccupato per la chiamata Array.Resize che creerà un altro array (vedi http://msdn.microsoft.com/en-us/library/1ffy6686(VS.80).aspx ). Questo è un passaggio non necessario se actualLength == Chunksize come sarà per tutti tranne l’ultimo blocco. Quindi suggerirei almeno:

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

Questo dovrebbe rimuovere molte allocazioni. Se la dimensione effettiva non è la stessa di chunkSize ma è ancora> 85000, la nuova matrice verrà allocata anche nell’heap di object Large, causandone potenzialmente il frammento e probabilmente causando perdite di memoria apparenti. Credo che ci vorrà ancora molto tempo per esaurire la memoria perché la perdita sarebbe piuttosto lenta.

Penso che una migliore implementazione sarebbe usare una specie di Buffer Pool per fornire gli array. Puoi eseguire il rollover (sarebbe troppo complicato), ma WCF ne fornisce uno per te. Ho riscritto leggermente il tuo codice per prendere l’avvento di questo:

 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); } } 

questo presuppone che l'implementazione di UploadFile possa essere riscritta per prendere un int per il no. di byte da scrivere

spero che questo possa essere d'aiuto

joe

Vedi anche RecyclableMemoryStream . Da questo articolo :

Microsoft.IO.RecyclableMemoryStream è una sostituzione MemoryStream che offre un comportamento superiore per i sistemi critici per le prestazioni. In particolare è ottimizzato per fare quanto segue:

  • Eliminare le allocazioni heap di oggetti di grandi dimensioni utilizzando i buffer raggruppati
  • Incorrere in un numero molto inferiore di GC di gen 2 e trascorrere molto meno tempo in pausa a causa di GC
  • Evita perdite di memoria avendo una dimensione del pool limitata
  • Evita la frammentazione della memoria
  • Fornire una debugabilità eccellente
  • Fornire metriche per il tracciamento delle prestazioni

Non sono così sicuro della prima parte della tua domanda ma, per quanto riguarda un modo migliore, hai considerato BITS ? Permette il download in background di file su http. Puoi fornirgli un http: // o file: // URI. È ripristinabile dal punto in cui è stato interrotto e scaricato in blocchi di byte utilizzando il metodo RANGE in http HEADER. Viene utilizzato da Windows Update. È ansible abbonarsi agli eventi che forniscono informazioni sullo stato di avanzamento e completamento.

Ho trovato un’altra soluzione per questo, fammi sapere cosa ne pensi!

Dal momento che non voglio avere grandi quantità di dati nella memoria, stavo cercando un modo elegante per memorizzare array di byte temporanei o un stream.

L’idea è di creare un file temporaneo (non hai bisogno di diritti specifici per farlo) e poi usarlo come un stream di memoria. Rendere la class Disposable pulirà il file temporaneo dopo che è stato utilizzato.

 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. } }