I have an XML file that I need to have concurrent access to. I do not need concurrent write, but I do need concurrent read. The perscribe
May I suggest a slightly different approach to it all. You can do the following with PowerShell which allows you to avoid the issue on the reads. My XML navigation may be a little off, but it will work as described here.
function Read-Only()
{
$database = [xml](Get-Content $filename)
foreach($item in $database.Items)
{
Write-Host "Item name $item.Name"
}
}
Now you can keep your same write function or you can do it all with PowerShell. If you wanted to do it with PowerShell you can do something like this.
function Read-Write()
{
$database = [xml](Get-Content $filename)
$itemname = Read-Host "Enter an item name ('quit' to quit)"
while($itemname -ne 'quit')
{
$newItem = $database.CreateElement("item")
$newItem.SetAttribute("Name", $itemname)
$database.Items.AppendChild($newItem)
# You can also save all the changes up and write once.
$database.Save($filename)
$itemname = Read-Host "Enter an item name ('quit' to quit)"
}
}
If you use [FileAccess]::Read
without specifying a file-share mode explicitly, then if the file is already open, opening it again will only succeed if it was originally opened with file-share mode [FileShare]::ReadWrite
(even though you're only asking for read access, the method defaults to requesting write access too) - whereas your Read-Write
function (sensibly) uses just [FileShare]::Read
.
Your immediate problem goes away if you explicitly open your read-only filestream with file-share mode [FileShare]::ReadWrite
:
[System.IO.FileStream]::new(
$path,
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::Read,
[System.IO.FileShare]::ReadWrite # !! required
)
This allows both other readers concurrent access as well as the one and only (read+)writer (which may have opened the file first).
However, a file getting rewritten while being read by others can be problematic, so for robust and predictable operation I suggest a different approach:
Note: This answer to a related question shows a simpler alternative to the solution below, which, however, takes longer to update the file.
Make modifications in a temporary copy of your file, then replace the original.
This requires explicit synchronization to coordinate between would-be updaters so as to serialize updates in order to prevent updates from overwriting each other.
You could achieve this with a separate lock file (sentinel file) named, say, updating
, which acts as an indicator to other would-be writers that an update is in progress.
Mike Christiansen (the OP himself) ended up also storing the username of the locking user in that file, to provide feedback to other would-be lockers.
When modification is requested:
Keep trying in a loop until creating lock file updating
succeeds, failing if the file already exists.
updating
file signals to other would-be writers that a modification has started. Readers, by contrast, can continue to read at this point.Create a (temporary) copy of the current XML file and perform the modifications there.
Replace the original file with the modified copy - use a retry loop until copying over / rewriting the original succeeds, given that other readers may temporarily prevent deleting (re-creating) / rewriting the original file.
Delete file updating
, signaling to other would-be modifiers that the update has completed.
try .. finally
), because letting it linger would block future updates; you may also need a timeout-based mechanism that on waiting for a preexisting file to be deleted eventually forces deletion, if it can be assumed that the previous updater has crashed.As for read access:
While no modifications are taking place, concurrent read access should work fine.
While the XML file is being replaced with the temporary copy / rewritten, opening the file for reading will fail for the duration of the file-copy operation / write operation, so you'll need a retry loop there too.
The code Mike ultimately used can be found here.
Maybe this can help. When you create a filestream there is an FileShare option. If you set it to ReadWrite, multiple processes can open that file
$fsMain = [System.IO.File]::Open("C:\stack\out.txt", "Open", "ReadWrite", "ReadWrite")
$fsReadOnly = [System.IO.File]::Open("C:\stack\out.txt", "Open", "Read", "ReadWrite")
Write-Host ("fsMain: CanRead=" + $fsMain.CanRead + ", CanWrite=" + $fsMain.CanWrite)
Write-Host ("fsReadOnly: CanRead= " + $fsReadOnly.CanRead + ", CanWrite=" + $fsReadOnly.CanWrite)
$fsMain.Close()
$fsReadOnly.Close()