2010-04-02 5 views
9

Ist das folgende Muster von Multithreadaufrufen für ein .Net FileStream akzeptabel?Synchronisierungsanforderungen für FileStream (Beginn/Ende) (Lesen/Schreiben)

Mehrere Threads eine Methode, wie dieser Aufruf:

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

Wo AcceptResults sind:

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

Ich bin verwirrt, weil die Dokumentation zu mir unklar erscheint. Zum Beispiel sagt die FileStream-Klasse:

Alle öffentlichen statischen Mitglieder dieses Typs sind threadsicher. Es ist nicht garantiert, dass alle Instanzmitglieder Thread-sicher sind.

Aber die Dokumentation für Beginread scheint mehrere Leseanforderungen im Flug zu betrachten ist:

Mehrere gleichzeitige asynchrone Anfragen die Anfrage Abschluss, um unsicher machen.

Sind Mehrfachlesevorgänge zulässig, oder nicht? Schreibt? Ist dies der geeignete Weg, um den Standort des Position des Streams zwischen dem Aufruf von Seek und dem Anruf an BeginRead zu sichern? Oder muss dieses Schloss bis EndRead gehalten werden, daher kann man nur im Flug lesen oder schreiben?

Ich verstehe, dass der Rückruf auf einem anderen Thread auftreten wird, und meine Handhabung von state, buffer behandeln, dass auf eine Weise, die mehrere im Flug Lesevorgänge erlauben würde.

Weiter, weiß jemand wo in der Dokumentation, um die Antworten auf diese Fragen zu finden? Oder ein Artikel von jemandem, der Bescheid weiß? Ich habe gesucht und kann nichts finden.

Relevante Dokumentation:

FileStream class
Seek method
BeginRead method
EndRead
IAsyncResult interface

Bearbeiten mit einigen neuen Informationen

Eine kurze Überprüfung mit Reflector zeigt, dass BeginRead die Stream-Position in den Status pro Anruf einbindet (einige Felder der NativeOverlapped-Struktur). Es scheint, dass EndRead die Stream-Position nicht konsultiert, zumindest nicht in irgendeiner offensichtlichen Weise. Dies ist natürlich nicht schlüssig, weil es nicht naheliegend sein könnte oder von der zugrunde liegenden nativen API nicht unterstützt werden könnte.

+0

+1 Gut geschriebene Frage. – SLaks

Antwort

1

Ja, die Dokumentation ist skizzenhaft. Keine Ahnung für bessere Dokumente, leider.

EDIT: Eigentlich Joe Duffy Buch Concurrent-Programmierung auf Windows hat Kapitel 8 APM, die die async API, IAsyncResult und so (gutes Buch und Autor) erklärt.Nach wie vor besteht das grundlegende Problem darin, dass MSDN angibt, dass Instanzvariablen nicht Thread-sicher sind und daher eine angemessene Synchronisierung erforderlich ist.

Sie haben also mehrere Threads BeginRead für die gleiche Instanz der Datei starten? Auf der BeginRead-Seite wird dies jedoch erwähnt: "EndRead muss genau einmal für jeden Aufruf von BeginRead aufgerufen werden. Wenn ein Lesevorgang vor dem Beginn eines anderen Lesevorgangs nicht beendet wird, kann dies zu unerwünschtem Verhalten führen, z. B. Deadlock." Außerdem rufen Sie Seek auf dem File-Objekt auf, während andere Threads möglicherweise in der Mitte ihrer BeginRead-Rückrufe ausgeführt werden. Überhaupt nicht sicher.

+1

Warum sollte es von Bedeutung sein, wenn ein Thread Seeking ist, während ein anderer seinen Callback ausführt? Vermutlich bedeutet der Rückruf, dass das angeforderte Lesen abgeschlossen ist, oder? Ich mache mir mehr Gedanken darüber, Seek in der Zeit zwischen einem BeginRead und dem passenden Callback aufzurufen. Wenn ich etwas nicht verpasse, ruft der obige Code EndRead genau einmal für jeden Aufruf von BeginRead auf, wobei eine gewisse Unsicherheit darüber besteht, ob BeginRead seinen Rückruf aufruft, wenn das von ihm zurückgegebene IAsyncResult CompletedSynchronous ist. –

+0

Ja, es hat ein EndRead für jedes BeginRead. Es gibt jedoch keine Garantie, dass die EndRead aufgerufen wird, bevor ein anderer Thread seine BeginRead startet, die Sperre schützt nicht für dieses Szenario. –

+0

Oh, Sie könnten hier OK sein, die BeginRead-Seite sagt "Unter Windows werden alle I/O-Operationen kleiner als 64 KB synchron für eine bessere Leistung abgeschlossen. Asynchronous I/O kann Leistung für Puffergrößen kleiner als 64 KB behindern." Sie garantieren also, dass alle Lesevorgänge tatsächlich von Ihrem Schloss serialisiert werden. Aber wenn alle Threads durch die Sperren und die synchronen Lesevorgänge serialisiert werden, warum überhaupt die async API verwenden? –