El intercambio de archivos no funciona como se esperaba

Tengo un problema de uso compartido de archivos en el que mi proceso intenta leer un archivo de registro mientras NLog aún está abierto. Al diagnosticar el problema, encontré algo sorprendente. Lo siguiente falla:

using (var fileStream1 = new FileStream("test.file", FileMode.Append, FileAccess.Write, FileShare.Read)) using (var fileStream2 = new FileStream("test.file", FileMode.Open, FileAccess.Read, FileShare.Read)) { } 

La segunda llamada al constructor de FileStream falla con:

 System.IO.IOException was unhandled Message=The process cannot access the file 'c:\...\test.file' because it is being used by another process. Source=mscorlib StackTrace: at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath) at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share) 

Esto es a pesar del hecho de que el primer FileStream indica que está dispuesto a compartir la lectura. Lo que encontré aún más sorprendente fue que esto funciona:

 using (var fileStream1 = new FileStream("test.file", FileMode.Append, FileAccess.Write, FileShare.Read)) using (var fileStream2 = new FileStream("test.file", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { } 

Um, sí, solicitar más acceso cuando se abre la segunda transmisión en realidad elude el problema. Estoy completamente desconcertado en cuanto a por qué ese es el caso, y solo puedo asumir que estoy entendiendo mal algo. He leído los documentos de la API pero solo son compatibles con mi modelo mental actual sobre cómo debería funcionar esto, al contrario de cómo funciona.

Aquí hay algunas citas de apoyo de los documentos :

Un uso típico de esta enumeración es definir si dos procesos pueden leer simultáneamente del mismo archivo. Por ejemplo, si se abre un archivo y se especifica Lectura, otros usuarios pueden abrir el archivo para leer pero no para escribir.

Aquí hay otra joya:

El siguiente constructor de FileStream abre un archivo existente y otorga acceso de solo lectura a otros usuarios (Leer).

FileStream s2 = new FileStream(name, FileMode.Open, FileAccess.Read, FileShare.Read);

¿Alguien puede arrojar alguna luz sobre este comportamiento. Estoy probando esto en .NET 4% de Windows XP.

  var fileStream2 = new FileStream(..., FileShare.Read) 

Esto hace tropezar a muchos progtwigdores. Todo el mundo asume que esta lectura añadida se comparte. No fue así, la solicitud de acceso al archivo original ya permitía leerlo y especificarlo de nuevo no cambia nada. En cambio, niega la escritura compartida. Y eso no puede funcionar porque alguien ya tiene acceso de escritura. Y lo está utilizando, no se puede quitar ese derecho. Así que su solicitud para acceder al archivo fallará.

Debe incluir FileShare.Write.

Lo que realmente sucede es que fileStream2 no puede cambiar el acceso subsiguiente a un archivo que ya está abierto para escritura (o fileStream1 ) por fileStream1 .

fileStream2 abrirá con éxito el archivo dejando un FileShare.Read como “legacy” para el acceso posterior solo si no hay procesos que ya tengan acceso de Write archivo. Aún más, en nuestro ejemplo estamos hablando del mismo proceso. No tendría mucho sentido modificar las propiedades de un flujo de archivos de otro flujo de archivos, ¿no es así?

Quizás la siguiente comparación lo explique aún mejor:

 // works using (var fileStream1 = new FileStream("test.file", FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite)) using (var fileStream2 = new FileStream("test.file", FileMode.Append, FileAccess.Write, FileShare.Read)) using (var fileStream3 = new FileStream("test.file", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { } // fails using (var fileStream1 = new FileStream("test.file", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)) using (var fileStream2 = new FileStream("test.file", FileMode.Append, FileAccess.Write, FileShare.Read)) using (var fileStream3 = new FileStream("test.file", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { } 

En mi opinión, la frase de descripción para FileShare.Read :

Permite la posterior apertura del archivo para su lectura.

debe leerse como

El acceso posterior al archivo está restringido solo para lectura, incluido el acceso a lockings ya existentes.

[Actualizar]

No he analizado el código, pero parece que estos dos enlaces podrían arrojar algo de luz sobre el funcionamiento interno del constructor:

El ctor interno de FileStream

El método interno de inicio de FileStream

Creo que he encontrado la respuesta en la documentación de CreateFile .

En la discusión del parámetro dwShareMode , dice:

FILE_SHARE_READ 0x00000001 Habilita las operaciones de apertura posteriores en un archivo o dispositivo para solicitar acceso de lectura. De lo contrario, otros procesos no pueden abrir el archivo o dispositivo si solicitan acceso de lectura. Si no se especifica este indicador, pero el archivo o dispositivo se ha abierto para el acceso de lectura, la función falla.

FILE_SHARE_WRITE 0x00000002 Permite las operaciones de apertura subsiguientes en un archivo o dispositivo para solicitar acceso de escritura. De lo contrario, otros procesos no pueden abrir el archivo o dispositivo si solicitan acceso de escritura. Si este indicador no se especifica, pero el archivo o dispositivo se ha abierto para el acceso de escritura o tiene una asignación de archivos con acceso de escritura, la función falla.

Eso cambia fundamentalmente mi comprensión de cómo funciona el intercambio de archivos.

Cuarto parámetro que pasas

compartir
Una constante que determina cómo se compartirá el archivo por procesos.

Determina en qué modo otros pueden abrir el archivo. Obviamente, cuando intenta abrir el archivo con el modo de compartir archivos “leer” y ya tiene el mismo archivo abierto en el modo de escritura, la operación falla.