2010-02-22 6 views
17

In passato ho sempre utilizzato un oggetto FileStream per scrivere o riscrivere un intero file dopo il quale chiuderei immediatamente lo stream. Tuttavia, ora sto lavorando a un programma in cui voglio mantenere aperto un FileStream per consentire all'utente di mantenere l'accesso al file mentre lavorano tra i salvataggi. (Vedi il mio previous question).Riutilizzo di un filestream

Sto usando XmlSerializer per serializzare le mie classi in un file da e XML. Ma ora sto mantenendo il FileStream aperto per essere usato per salvare (reserializzare) la mia istanza di classe più tardi. Ci sono delle considerazioni particolari che devo fare se sto riusando lo stesso file stream più e più volte, rispetto all'uso di un nuovo flusso di file? Devo reimpostare il flusso all'inizio tra i salvataggi? Se un salvataggio successivo è di dimensioni inferiori rispetto al salvataggio precedente, FileStream lascerà i restanti byte dal vecchio file e quindi creerà un file danneggiato? Devo fare qualcosa per cancellare il file in modo che si comporti come se stessi scrivendo un file completamente nuovo ogni volta?

risposta

14

Il tuo sospetto è corretto: se reimposti la posizione di un flusso di file aperto e scrivi contenuti più piccoli di quelli già presenti nel file, lascerà i dati in coda e genererà un file corrotto (a seconda della definizione di "corrotto" ", ovviamente).

Se si desidera sovrascrivere il file, è necessario chiudere lo stream al termine e creare un nuovo flusso quando si è pronti a ri-salvarlo.

Ho notato dalla domanda collegata che il file è stato aperto per impedire ad altri utenti di scrivervi allo stesso tempo. Probabilmente questa non sarebbe la mia scelta, ma se hai intenzione di farlo, allora io penso che puoi "cancellare" il file invocando stream.SetLength(0) tra i salvataggi successivi.

+0

Quale sarebbe la vostra scelta allora? Non voglio consentire a due utenti di aprire il file allo stesso tempo pensando di avere entrambi accesso in scrittura e di sovrascrivere i salvataggi dell'altro? –

+0

Una delle risposte più comuni è creare un "file di blocco" a lunghezza zero. Questo può essere combinato mantenendo un file "FileStream" aperto a tempo indeterminato, ovviamente, ma molte app scelgono solo il file di blocco perché può essere sovrascritto se necessario. Immagino dipenda dalla natura della tua app e dell'ambiente di condivisione - forse è davvero l'opzione migliore. – Aaronaught

+0

Set-length (0) sembra fare ciò che sto cercando di fare. –

0

In base alla domanda, penso che sarebbe meglio servire per chiudere/riaprire il file sottostante. Sembra che tu non stia facendo altro che scrivere l'intero file. Il valore che puoi aggiungere riscrivendo Open/Close/Flush/Seek sarà vicino a 0. Concentrati sul tuo problema aziendale.

+0

Sì, sto solo riscrivendo l'intero file. Quindi suggeriresti di tenere aperto lo stream per bloccare l'accesso al file e quindi chiudere e riaprire rapidamente lo stream quando devo scrivere di nuovo il file? –

+0

Perché vuoi mantenere aperto il file? Quale scopo commerciale serve? –

+0

Non vuole che un altro processo acquisisca il file dopo che è stato chiuso e per scrivere in esso sovrascrivendo le modifiche che un altro processo sta facendo attualmente. – Despertar

7

Un'altra opzione potrebbe essere quella di utilizzare SetLength (0) per troncare il file prima di iniziare a riscriverlo.

10

Ci sono vari modi per farlo; se si sono ri-apertura del file, forse impostarlo per troncare:

using(var file = new FileStream(path, FileMode.Truncate)) { 
    // write 
} 

Se si sovrascrive il file mentre già aperto, poi basta tagliarlo dopo aver scritto:

file.SetLength(file.Position); // assumes we're at the new end 

vorrei cercare di evitare di cancellare/ricreare, dal momento che questo perde qualsiasi ACL ecc.

1

Recentemente si è imbattuto nello stesso requisito. Infatti, in precedenza, ero solito creare un nuovo FileStream all'interno di un'istruzione using e sovrascrivere il file precedente. Sembra la cosa semplice ed efficace da fare.

using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write) 
{ 
    ProtoBuf.Serializer.Serialize(stream , value); 
} 

Tuttavia, ho riscontrato problemi di blocco in cui un altro processo blocca il file di destinazione. Nel mio tentativo di contrastare ciò, ho ripetuto la scrittura diverse volte prima di spingere l'errore in cima allo stack.

int attempt = 0; 
while (true) 
{ 
    try 
    { 
     using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write) 
     { 
     ProtoBuf.Serializer.Serialize(stream , value); 
     } 
     break; 
    } 
    catch (IOException) 
    { 
     // could be locked by another process 
     // make up to X attempts to write the file 
     attempt++; 
     if (attempt >= X) 
     { 
     throw; 
     } 
     Thread.Sleep(100); 
    } 
} 

Sembrava funzionare per quasi tutti. Poi quella macchina del problema arrivò e mi costrinse a seguire il percorso di mantenere un blocco sul file per tutto il tempo.Quindi, invece di riprovare a scrivere il file nel caso in cui sia già bloccato, ora mi sto assicurando di ottenere e tenere aperto lo stream in modo che non ci siano problemi di blocco con le scritture successive.

int attempt = 0; 
while (true) 
{ 
    try 
    { 
     _stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read); 
     break; 
    } 
    catch (IOException) 
    { 
     // could be locked by another process 
     // make up to X attempts to open the file 
     attempt++; 
     if (attempt >= X) 
     { 
     throw; 
     } 
     Thread.Sleep(100); 
    } 
} 

Ora quando scrivo il file nella posizione FileStream deve essere resettato a zero, come ha detto Aaronaught. Ho deciso di "cancellare" il file chiamando lo _stream.SetLength(0). Sembrava la scelta più semplice. Quindi, utilizzando il nostro serializzatore di scelta, Marc Gravell's protobuf-net, serializza il valore nello stream.

_stream.SetLength(0); 
ProtoBuf.Serializer.Serialize(_stream, value); 

Questo funziona bene la maggior parte del tempo e il file è completamente scritto sul disco. Tuttavia, in alcune occasioni ho osservato che il file non viene immediatamente scritto sul disco. Per garantire che il flusso venga svuotato e che il file sia completamente scritto su disco, dovevo chiamare anche _stream.Flush(true).

_stream.SetLength(0); 
ProtoBuf.Serializer.Serialize(_stream, value); 
_stream.Flush(true);