2010-05-12 14 views
14

Devo trasferire file di grandi dimensioni tra computer su connessioni non affidabili tramite WCF.Memorystream e heap di oggetti di grandi dimensioni

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. aperta filestream
  2. lettura pezzo da file in byte [] e creare MemoryStream
  3. trasferimento pezzo
  4. torna a 2. fino a quando l'intero file viene inviato

Il mio problema è nel passaggio 2. Presumo che quando creo un flusso di memoria da un array di byte, finirà sul LOH e l'ultima causa solo un'eccezione eccezionale. Non sono riuscito a creare questo errore, forse mi sbaglio nella mia ipotesi.

Ora, non voglio inviare il byte [] nel messaggio, poiché 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.

mia domanda reale (s):

  • La mia soluzione attuale creare oggetti sul LOH e volontà che mi causare problemi?
  • C'è un modo migliore per risolvere questo?

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

Edit:

soluzione attuale:

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

risposta

35

Spero che tutto sia a posto. È la mia prima risposta su StackOverflow.

Sì, assolutamente se il tuo chunk è superiore a 85000 byte, la matrice verrà allocata nell'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 (vedere 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 oggetto 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 un'implementazione migliore sarebbe quella di utilizzare una sorta di buffer pool per fornire gli array. Puoi eseguire il rollover (sarebbe troppo complicato), ma WCF ne fornisce uno per te. Ho riscritto il codice un po 'a prendere advatage di che:

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'attuazione di UploadFile può essere riscritta a prendere un int per il no. di byte da scrivere

Spero che questo aiuta

joe

+1

Grazie mille! Questa è una risposta davvero brillante. Grazie per aver segnalato il problema Array.Resize. Inoltre non ho mai sentito parlare di BufferManager, sembra che questo mi aiuterà molto anche in altre aree. Questo è molto più di quanto mi aspettassi, quindi ho pensato di iniziare una piccola taglia e darla a te, ma devo aspettare 23 ore dopo aver iniziato una taglia ...Quindi devi aspettare anche tu :) – flayn

+0

Grazie per quello. Sono davvero felice di poter essere d'aiuto. Fammi sapere se c'è qualcos'altro. Guardando indietro, potrebbe valere la pena sottolineare che l'implementazione ottimale dovrebbe condividere una singola istanza di BufferManager attraverso l'intero servizio. Non so quanto sarebbe pratico per te. –

+0

+1 Ho appena imbattuto in questo in cerca di una risposta a un problema simile. Mai sentito parlare di BufferManager prima - fantastico! Credo che questo sarà qualcosa da ricordare per il futuro. –

2

io non sono così sicuro circa la prima parte della sua domanda, ma come un modo migliore - avete 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. È possibile abbonarsi agli eventi che forniscono informazioni sullo stato di avanzamento e completamento.

+0

Grazie per il suggerimento, ma non voglio installare IIS su ogni macchina. – flayn

+2

Nessun problema, solo un pensiero. Solo per indicare che hai solo bisogno di IIS su ogni macchina se stanno caricando. Se i client eseguono il download solo tramite BITS, non richiedono IIS. –

1

Sono venuto con un'altra soluzione per questo, fatemi sapere cosa ne pensate!

Poiché non desidero avere grandi quantità di dati nella memoria, cercavo un modo elegante per archiviare gli array di byte temporanei o uno stream.

L'idea è di creare un file temporaneo (non è necessario disporre di diritti specifici per farlo) e quindi utilizzarlo come un flusso di memoria. Rendere la classe 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. 
    } 
    } 
2

Vedi anche RecyclableMemoryStream. Da this article:

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

  • Eliminare grandi allocazioni Object Heap utilizzando tamponi in pool
  • incorrere gran lunga meno Gen 2 GC, e spendere molto meno tempo di pausa a causa di GC
  • perdite di memoria
  • Evita avendo una dimensione del pool limitato
  • memoria evitare la frammentazione
  • fornire un eccellente debuggability
  • fornire metriche per il monitoraggio delle prestazioni
Problemi correlati