.NET (PowerShell) 并发文件使用(锁定,同时仍允许读取访问)
.NET (PowerShell) Concurrent file usage (locking, while still allowing read access)
我有一个 XML 文件需要同时访问。我不需要并发写入,但我需要并发读取。 reading/modifying 这个 XML 文件的指定方法是使用 PowerShell 脚本,该脚本使用 Linq to XML (XElement) 使用 XML 文件。
理想的场景是:
- 团队中的所有用户将在他们的工作日开始时 运行 此 PowerShell 脚本,并且它将保持 运行ning 直到工作日结束。
- 在整个工作日,团队中的任何用户都将输入预定义的命令来查询 XML 文件以收集数据。每次用户 运行 进行这些查询之一时,它将打开 XML 文件,执行查询,关闭 XML 文件,然后显示结果。
- 偶尔,在一天中,这些用户中的任何一个都需要修改 XML 文件。用户将输入一个预定义的命令,指定他们想要修改的节点,然后他们将输入他们想要的所有数据 add/modify。然后脚本将打开文件(锁定它,所以没有其他用户可以打开它 read/write),将 XML 文件读入
XElement
对象,执行所需的操作,然后保存XElement
回到文件,然后释放锁。
- 任何时候,当用户正在修改文件时(某些操作可能会很长),任何其他试图进行修改的用户不得能够打开文件文件(我将捕获异常,并显示 "Please wait a few moments" 类型的消息)。但是,他们必须能够以只读方式打开文件以执行查询。
我已经看到 ,并尝试使用以下代码实现它。在 PowerShell ISE 的一个实例中,我执行了读写函数,然后在 "Enter an item name" 循环中,我在另一个 PowerShell ISE 实例中执行了只读函数。尝试以只读方式打开文档时,我收到异常 The process cannot access the file 'C:\Path\To\file.xml' because it is being used by another process.
using namespace System.Linq.Xml
using namespace System.IO
Add-Type -AssemblyName 'System.Xml.Linq (rest of Assembly's Full Name)'
$filename = "C:\Path\To\file.xml"
function Read-Only()
{
$filestream = [FileStream]::new($filename, [FileMode]::Open, [FileAccess]::Read)
$database = [XElement]::Load($filestream)
foreach($item in $database.Element("Items").Elements("Item"))
{
Write-Host "Item name $($item.Attribute("Name").Value)"
}
$filestream.Close()
$filestream.Dispose()
}
function Read-Write()
{
$filestream = [FileStream]::new($filename, [FileMode]::Open, [FileAccess]::ReadWrite, [FileShare]::Read)
$database = [XElement]::Load($filestream)
$itemname = Read-Host "Enter an item name ('quit' to quit)"
while($itemname -ne 'quit')
{
$database.Element("Items").Add([XElement]::new([XName]"Item", [XAttribute]::new([XName]"Name", $itemname)))
$itemname = Read-Host "Enter an item name ('quit' to quit)"
}
$filestream.Seek(0, [SeekOrigin]::Begin)
$database.Save($filestream)
$filestream.Close()
$filestream.Dispose()
}
如何锁定文件进行独占编辑,同时仍允许对任何其他客户端进行只读访问?
编辑: 我已经使用 mklement0 建议的解决方案解决了这个问题 - 使用单独的文件作为锁定文件。代码张贴在这里:https://pastebin.com/ytzGE7se
我可以建议一个稍微不同的方法来解决这一切。您可以使用 PowerShell 执行以下操作,从而避免读取问题。我的 XML 导航可能有点不对劲,但它会像描述的那样工作 here。
function Read-Only()
{
$database = [xml](Get-Content $filename)
foreach($item in $database.Items)
{
Write-Host "Item name $item.Name"
}
}
现在您可以保留相同的写入功能,也可以使用 PowerShell 完成所有操作。如果你想用 PowerShell 来做,你可以这样做。
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)"
}
}
也许这会有所帮助。创建文件流时,有一个 FileShare 选项。如果将其设置为读写,则多个进程可以打开该文件
$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()
如果您使用[FileAccess]::Read
而没有明确指定文件共享模式,那么如果文件已经打开,只有当它最初是使用文件共享模式打开时,再次打开它才会成功[FileShare]::ReadWrite
(即使您只请求 read 访问权限,方法 defaults 请求 write 访问权限也是) - 而你的 Read-Write
函数(明智地)只使用 [FileShare]::Read
.
如果您使用文件共享模式显式打开只读文件流,您眼前的问题就会消失[FileShare]::ReadWrite
:
[System.IO.FileStream]::new(
$path,
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::Read,
[System.IO.FileShare]::ReadWrite # !! required
)
这允许其他读者并发访问以及唯一的 (read+)writer(可能首先打开文件)。
但是,一个文件在被其他人读取时被重写可能会有问题,因此为了稳健和可预测操作 我建议不同的方法:
注意:相关问题的 显示了一个更简单的替代下面的解决方案,但是,更新文件需要更长的时间。
在文件临时副本中进行修改,然后替换原始文件。
这 需要显式同步来协调潜在的 更新者 以便 序列化 更新 以防止更新相互覆盖。
您可以使用 一个单独的锁定文件 (哨兵文件)来实现这一点,例如,命名为 updating
,它可以向其他潜在的作者指示更新正在进行中.
Mike Christiansen(OP 本人)最终还将锁定用户的用户名存储在该文件中,以向其他可能的储物柜提供反馈。
当请求修改时:
循环尝试直到创建锁定文件updating
成功,如果文件已经存在则失败。
- (重新)-创建
updating
文件向其他可能的作者发出修改已经开始的信号。相比之下,读者此时可以继续阅读。
创建当前 XML 文件的(临时)副本 并在其中执行修改。
- Mike 自己最终只是简单地修改了文件的 内存中 副本,这简化了事情(如果您有足够的内存来读取整个文件)。
用修改后的副本替换原始文件 - 使用重试循环直到复制/重写原始文件成功,因为其他读者可能会暂时阻止删除(重新创建)/重写原始文件.
删除文件 updating
,向其他可能的修改者发出更新已完成的信号。
- 确保文件总是清理(
try .. finally
),因为让它逗留会阻止未来的更新;您可能还需要一个基于超时的机制,如果可以假定先前的更新程序已崩溃,则在等待最终删除预先存在的文件时 强制 删除。
至于阅读访问:
虽然没有进行任何修改,但并发读取访问应该可以正常工作。
当 XML 文件被临时副本替换/重写时,在文件复制操作期间打开文件进行读取将失败 / 写操作,所以你也需要一个重试循环。
可以找到 Mike 最终使用的代码 here。
我有一个 XML 文件需要同时访问。我不需要并发写入,但我需要并发读取。 reading/modifying 这个 XML 文件的指定方法是使用 PowerShell 脚本,该脚本使用 Linq to XML (XElement) 使用 XML 文件。
理想的场景是:
- 团队中的所有用户将在他们的工作日开始时 运行 此 PowerShell 脚本,并且它将保持 运行ning 直到工作日结束。
- 在整个工作日,团队中的任何用户都将输入预定义的命令来查询 XML 文件以收集数据。每次用户 运行 进行这些查询之一时,它将打开 XML 文件,执行查询,关闭 XML 文件,然后显示结果。
- 偶尔,在一天中,这些用户中的任何一个都需要修改 XML 文件。用户将输入一个预定义的命令,指定他们想要修改的节点,然后他们将输入他们想要的所有数据 add/modify。然后脚本将打开文件(锁定它,所以没有其他用户可以打开它 read/write),将 XML 文件读入
XElement
对象,执行所需的操作,然后保存XElement
回到文件,然后释放锁。 - 任何时候,当用户正在修改文件时(某些操作可能会很长),任何其他试图进行修改的用户不得能够打开文件文件(我将捕获异常,并显示 "Please wait a few moments" 类型的消息)。但是,他们必须能够以只读方式打开文件以执行查询。
我已经看到 The process cannot access the file 'C:\Path\To\file.xml' because it is being used by another process.
using namespace System.Linq.Xml
using namespace System.IO
Add-Type -AssemblyName 'System.Xml.Linq (rest of Assembly's Full Name)'
$filename = "C:\Path\To\file.xml"
function Read-Only()
{
$filestream = [FileStream]::new($filename, [FileMode]::Open, [FileAccess]::Read)
$database = [XElement]::Load($filestream)
foreach($item in $database.Element("Items").Elements("Item"))
{
Write-Host "Item name $($item.Attribute("Name").Value)"
}
$filestream.Close()
$filestream.Dispose()
}
function Read-Write()
{
$filestream = [FileStream]::new($filename, [FileMode]::Open, [FileAccess]::ReadWrite, [FileShare]::Read)
$database = [XElement]::Load($filestream)
$itemname = Read-Host "Enter an item name ('quit' to quit)"
while($itemname -ne 'quit')
{
$database.Element("Items").Add([XElement]::new([XName]"Item", [XAttribute]::new([XName]"Name", $itemname)))
$itemname = Read-Host "Enter an item name ('quit' to quit)"
}
$filestream.Seek(0, [SeekOrigin]::Begin)
$database.Save($filestream)
$filestream.Close()
$filestream.Dispose()
}
如何锁定文件进行独占编辑,同时仍允许对任何其他客户端进行只读访问?
编辑: 我已经使用 mklement0 建议的解决方案解决了这个问题 - 使用单独的文件作为锁定文件。代码张贴在这里:https://pastebin.com/ytzGE7se
我可以建议一个稍微不同的方法来解决这一切。您可以使用 PowerShell 执行以下操作,从而避免读取问题。我的 XML 导航可能有点不对劲,但它会像描述的那样工作 here。
function Read-Only()
{
$database = [xml](Get-Content $filename)
foreach($item in $database.Items)
{
Write-Host "Item name $item.Name"
}
}
现在您可以保留相同的写入功能,也可以使用 PowerShell 完成所有操作。如果你想用 PowerShell 来做,你可以这样做。
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)"
}
}
也许这会有所帮助。创建文件流时,有一个 FileShare 选项。如果将其设置为读写,则多个进程可以打开该文件
$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()
如果您使用[FileAccess]::Read
而没有明确指定文件共享模式,那么如果文件已经打开,只有当它最初是使用文件共享模式打开时,再次打开它才会成功[FileShare]::ReadWrite
(即使您只请求 read 访问权限,方法 defaults 请求 write 访问权限也是) - 而你的 Read-Write
函数(明智地)只使用 [FileShare]::Read
.
如果您使用文件共享模式显式打开只读文件流,您眼前的问题就会消失[FileShare]::ReadWrite
:
[System.IO.FileStream]::new(
$path,
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::Read,
[System.IO.FileShare]::ReadWrite # !! required
)
这允许其他读者并发访问以及唯一的 (read+)writer(可能首先打开文件)。
但是,一个文件在被其他人读取时被重写可能会有问题,因此为了稳健和可预测操作 我建议不同的方法:
注意:相关问题的
在文件临时副本中进行修改,然后替换原始文件。
这 需要显式同步来协调潜在的 更新者 以便 序列化 更新 以防止更新相互覆盖。
您可以使用 一个单独的锁定文件 (哨兵文件)来实现这一点,例如,命名为 updating
,它可以向其他潜在的作者指示更新正在进行中.
Mike Christiansen(OP 本人)最终还将锁定用户的用户名存储在该文件中,以向其他可能的储物柜提供反馈。
当请求修改时:
循环尝试直到创建锁定文件
updating
成功,如果文件已经存在则失败。- (重新)-创建
updating
文件向其他可能的作者发出修改已经开始的信号。相比之下,读者此时可以继续阅读。
- (重新)-创建
创建当前 XML 文件的(临时)副本 并在其中执行修改。
- Mike 自己最终只是简单地修改了文件的 内存中 副本,这简化了事情(如果您有足够的内存来读取整个文件)。
用修改后的副本替换原始文件 - 使用重试循环直到复制/重写原始文件成功,因为其他读者可能会暂时阻止删除(重新创建)/重写原始文件.
删除文件
updating
,向其他可能的修改者发出更新已完成的信号。- 确保文件总是清理(
try .. finally
),因为让它逗留会阻止未来的更新;您可能还需要一个基于超时的机制,如果可以假定先前的更新程序已崩溃,则在等待最终删除预先存在的文件时 强制 删除。
- 确保文件总是清理(
至于阅读访问:
虽然没有进行任何修改,但并发读取访问应该可以正常工作。
当 XML 文件被临时副本替换/重写时,在文件复制操作期间打开文件进行读取将失败 / 写操作,所以你也需要一个重试循环。
可以找到 Mike 最终使用的代码 here。