Search code examples
c#jsonmultithreadinglockingfilestream

How do you lock a file in C# without making it unwritable after first being read?


This question stems from this other thread: How to lock a file with C#?

Basically let's say you want to lock a JSON file, read it, then write to it afterward, and finally unlock it. You can lock the file using the answers from the other question.

However I'm having trouble where this is allowing me to read the file, but not write to it afterward without first unlocking the file. That is, the recommended method, which seems to be fairly well-respected, is locking the same thread out of its own resource.

Example:

using (var fs = new FileStream(GetJsonPath(), FileMode.Open, FileAccess.ReadWrite,
        FileShare.None))
{
    SomeDtoType dto;
    using (StreamReader reader = new StreamReader(fs))
    {
        dto = ((SomeDtoType)(new JsonSerializer()).Deserialize(reader,
                typeof(SomeDtoType)));
    }

    // Make changes to the DTO.....

    using (StreamWriter writer = new StreamWriter(fs))
    {
        new JsonSerializer().Serialize(writer, dto);
    }
}

The using line that creates the StreamWriter throws the following exception:

Stream was not writable.

Now one thing that comes to mind is the value of FileShare.None. The problem here is that that particular enum is evidently setting lock permissions for more than just external processes.

How can you lock external threads/processes out of changing/deleting the file, yet allow your own to make these two subsequent read/write accesses?

EDIT:

Evidently moving everything into the using block for the StreamReader, then setting fs.Position to 0 between the read and the write kind of fixes the issue. The fs.Position part is fine, but having to move the write logic into the using block for the StreamReader, just so they can both use the same FileStream lock, seems a tad odd...


Solution

  • StreamReader closes the stream if you don't use ctor overload and instruct it don't do that:

    using (StreamReader reader = new StreamReader(fs, Encoding.UTF8, true, 4096, leaveOpen:true))
    

    There is no finalizer in StreamReader, you can move it out using block and keep as undisposed, however I'd recommend explicitly control the lifetime and behavior.

    Another issue is that you'll append to the file. If you want to override a content you need to reset it before you write to it:

    fs.SetLength(0);