2010-04-02 5 views
9

Il seguente schema di chiamate multi-thread è accettabile per .Net FileStream?Requisiti di sincronizzazione per FileStream. (Inizio/Fine) (Lettura/Scrittura)

Fili chiamando un metodo come questo:

ulong offset = whatever; // different for each thread 
byte[] buffer = new byte[8192]; 
object state = someState; // unique for each call, hence also for each thread 

lock(theFile) 
{ 
    theFile.Seek(whatever, SeekOrigin.Begin); 
    IAsyncResult result = theFile.BeginRead(buffer, 0, 8192, AcceptResults, state); 
} 

if(result.CompletedSynchronously) 
{ 
    // is it required for us to call AcceptResults ourselves in this case? 
    // or did BeginRead already call it for us, on this thread or another? 
} 

Dove AcceptResults è:

void AcceptResults(IAsyncResult result) 
{ 
    lock(theFile) 
    { 
     int bytesRead = theFile.EndRead(result); 

     // if we guarantee that the offset of the original call was at least 8192 bytes from 
     // the end of the file, and thus all 8192 bytes exist, can the FileStream read still 
     // actually read fewer bytes than that? 

     // either: 
     if(bytesRead != 8192) 
     { 
      Panic("Page read borked"); 
     } 

     // or: 
     // issue a new call to begin read, moving the offsets into the FileStream and 
     // the buffer, and decreasing the requested size of the read to whatever remains of the buffer 
    } 
} 

Sono confuso perché la documentazione sembra chiaro per me. Ad esempio, la classe FileStream dice:

Qualsiasi membro statico pubblico di questo tipo è thread-safe. Non è garantito che tutti i membri di istanza siano thread-safe.

Ma la documentazione per BeginRead sembra contemplare avere più richieste di lettura in volo:

più richieste asincrone simultanee rendono l'ordine di completamento richiesta incerta.

Sono consentite più letture in volo o no? Scrive? È questo il modo appropriato per proteggere la posizione dello Position dello stream tra la chiamata a Seek e la chiamata a BeginRead? O questo blocco deve essere mantenuto fino allo EndRead, quindi solo una lettura o scrittura in volo alla volta?

Capisco che il callback si verificherà su un thread diverso, e la mia gestione di state, buffer gestisce quello in un modo che consentirebbe più letture in volo.

Inoltre, qualcuno sa dove nella documentazione trovare le risposte a queste domande? O un articolo scritto da qualcuno che sa? Ho cercato e non riesco a trovare nulla.

documentazione pertinente:

FileStream class
Seek method
BeginRead method
EndRead
IAsyncResult interface

Modifica con alcune nuove informazioni

Un controllo rapido con Reflector mostra che BeginRead acquisisce la posizione del flusso in stato per chiamata (alcuni campi della struttura NativeOverlapped). Sembra che EndRead non consulta la posizione del flusso, almeno non in modo evidente. Questo non è conclusivo, ovviamente, perché potrebbe essere in un modo non ovvio o potrebbe non essere supportato dall'API nativa sottostante.

+0

+1 Domanda ben scritta. – SLaks

risposta

1

Sì, la documentazione è approssimativa. Sfortunatamente non c'è un indizio per documenti migliori.

MODIFICA: in realtà il libro Concurrent Programming su Windows di Joe Duffy ha il capitolo 8 APM che spiega l'API asincrona, IAsyncResult e simili (buon libro e autore).Ancora il problema fondamentale qui è che MSDN dice che le variabili di istanza non sono thread-safe, quindi la necessità di una sincronizzazione appropriata.

Quindi hai avviato più thread BeginRead sulla stessa istanza di File? La pagina BeginRead menziona questo tuttavia: "EndRead deve essere chiamato esattamente una volta per ogni chiamata a BeginRead. Non riuscire a terminare un processo di lettura prima di iniziare un'altra lettura può causare comportamenti indesiderati come deadlock." Inoltre stai chiamando Seek sull'oggetto File mentre altri thread potrebbero essere nel mezzo dell'esecuzione dei loro callback BeginRead. Non è affatto sicuro.

+1

Perché dovrebbe importare se un thread è Seeking mentre un altro sta eseguendo la sua callback? Presumibilmente il callback significa che la lettura richiesta è completa, giusto? Sono più preoccupato di chiamare Seek nel tempo tra BeginRead e il callback corrispondente. A meno che non manchi qualcosa, il codice sopra chiama EndRead esattamente una volta per ogni chiamata a BeginRead, formando qualche incertezza sul fatto che BeginRead invochi la sua callback quando IAsyncResult restituisce CompletedSynchronously. –

+0

Sì, ha una EndRead per ogni BeginRead. Tuttavia, non vi è alcuna garanzia che EndRead venga chiamato prima che un altro thread avvii BeginRead, il blocco non protegge per quello scenario. –

+0

Oh, potresti essere OK qui, la pagina BeginRead dice "Su Windows, tutte le operazioni di I/O inferiori a 64 KB verranno completate in modo sincrono per prestazioni migliori. L'I/O asincrono potrebbe ostacolare le prestazioni per dimensioni del buffer inferiori a 64 KB." quindi * stai * garantendo che tutte le letture siano effettivamente serializzate dal tuo lucchetto. Ma se tutti i thread sono serializzati tramite i lock e le letture sincrone, perché preoccuparsi di utilizzare l'API async? –