对XML的并发读/写访问



我正试图通过在多台机器上同时运行的多个进程来实现XML文件中的信息更新。我的想法是循环10分钟,尝试打开和锁定文件,以便在1秒内随机写入。一旦文件被打开并锁定,我就会加载所有的XML,添加当前机器的信息,对XML进行排序,然后转售并移除锁定,这样下一台机器就可以打开了。问题是Get-Content不会锁定文件,因此两台机器可以加载相同的XML,而不是第二台机器加载带有第一台机器数据的XML。我发现了这个,它提供了一种锁定文件的方法,然后通过流读取,但当我尝试修改到这个时

$file = [IO.File]::Open($path, 'Open', 'ReadWrite', 'None')
$xml = Get-Content $path

我收到一个错误,因为文件已锁定。"获取内容"似乎不会锁定文件,但它确实尊重已经存在的锁定。那么,有没有一种方法可以锁定文件,这样只有机器锁定才能读写?也许更重要的是,这是正确的方法吗,还是有其他方法可以访问多个XML?这似乎是一种常见的情况,因此必须有一些最佳实践方法来做到这一点,即使没有本机cmdlet方法。FWIW,我必须支持PowerShell 2.0,这无疑限制了我如何处理这一问题。

EDIT:嗯,[io.file]位中的第三个参数的Read似乎不起作用。我现在有这个

$path = '\PxSupportPx ToolsResourcesjobs.xml'
foreach ($i in 1..10) {
$sleepTime = get-random -minimum:2 -maximum:5
$file = [IO.File]::Open($path, 'Open', 'ReadWrite', 'Read')
[xml]$xml = Get-Content $path
$newNode = $xml.createElement('Item')
$newNode.InnerXml = "$id : $i : $sleepTime : $(Get-Date)"
$xml.DocumentElement.AppendChild($newNode) > $null
$xml.Save($path)
$file.Close()
}

理论上,它应该采用我拥有的XML,带有两个伪日志项,读取它,附加另一个日志项(带有ID、迭代、睡眠时间和时间戳),并重复10次,其间随机睡眠。它试图用节省开支

"The process cannot access the file '\PxSupportPx ToolsResourcesjobs.xml' because it is being used by another process."

我真的想做一些以前没有做过1000次的事情吗?

好吧,根据评论,我现在的处境是这样的。我想确保在处理过程中不能(轻松)手动编辑原件。所以我已经实现了这一点。1:查找sentinel文件,如果未找到2:锁定原始文件,使其无法修改3:将原件复制为sentinel文件4:根据需要修改sentinel文件5:解锁原件6:将sentinel文件复制到原始文件上7:删除哨兵

在我看来,不确定的是,是否有人在解锁原件和复制哨兵之间手动修改原件,这是极不可能的。但是,似乎应该有一种100%确定的方法来处理这个问题,我想不出有没有哨兵文件的方法。

一般来说:文件并没有像数据库那样针对并发访问进行优化,所以如果您需要一些复杂的并发访问,您需要自己进行。

对一个密切相关的问题的回答演示了使用单独的锁文件(sentinel文件)来管理并发性,使中断最小化

但是,如果您愿意在读取、修改和保存修改的整个期间对文件设置独占锁定,则可以简化方法,并消除对锁定文件的需要

相比之下,锁定文件方法允许在其他进程读取文件的同时读取和准备修改,并且只需要对重写/替换文件的实际操作使用独占锁。

然而,对于这两种方法,都需要对文件进行一段独占锁定,以防止在重写文件时读卡器从文件中读取的不可预测性。

也就是说,您仍然需要所有相关流程的合作

  • 写入程序需要处理(暂时)无法以独占方式打开文件的问题,即当其他进程(读取器或写入程序)正在使用该文件时。

  • 类似地,阅读器必须准备好处理(临时)无法打开文件的情况(当写入程序正在更新文件时)。

关键是:

  • 使用文件共享模式None打开文件(即,在打开文件时拒绝其他进程使用同一文件),并保持打开状态,直到更新完成。这确保了从跨流程的角度来看操作是原子操作。

  • 仅使用[System.IO.File]::Open()返回的FileStream实例来读取和写入文件(调用cmdlet或.NET方法(如System.Xml.XmlDocument.Save())将失败,因为它们自己将尝试打开文件,然后以独占方式锁定)。


以下是实现独占锁定的代码的固定版本:

$path = '\PxSupportPx ToolsResourcesjobs.xml'
foreach ($i in 1..10) {
$sleepTime = get-random -minimum:2 -maximum:5
# Open the file with an exclusive lock so that no other process will be
# be able to even read it while an update is being performed.
# Use a RETRY LOOP until exclusive locking succeeds.
# You'll need a similar loop for *readers*.
# Note: In production code, you should also implement a TIMEOUT.
do {  # retry loop
try {
$file = [IO.File]::Open($path, 'Open', 'ReadWrite', 'None')
} catch {
# Did opening fail due to the file being LOCKED? -> keep trying.
if ($_.Exception.InnerException -is [System.IO.IOException] -and ($_.Exception.InnerException.HResult -band 0x21) -in 0x21, 0x20) { 
$host.ui.Write('.') # Some visual feedback
Start-Sleep -Milliseconds 500 # Sleep a little.
continue # Try again.
}
Throw # Unexpexted error -> rethrow.
}
break # Opening with exclusive lock succeeded, proceed below.
} while ($true)

# Read the file's content into an XML document (DOM).
$xml = New-Object xml # xml is a type accelerator for System.XML.XMLDocument
$xml.Load($file)
# Modify the XML document.
$newNode = $xml.createElement('Item')
$newNode.InnerXml = "$id : $i : $sleepTime : $(Get-Date)"
$null = $xml.DocumentElement.AppendChild($newNode)
# Convert the XML document back to a string
# and write that string back to the file.
$file.SetLength(0) # truncate existing content first
$xml.Save($file)
# Close the file and release the lock.
$file.Close()
}

至于您尝试了什么

$file = [IO.File]::Open($path, 'Open', 'ReadWrite', 'Read')以允许其他进程读取访问但不允许写入访问的方式打开文件。

然后,在$file仍处于打开状态时调用$xml.Save($path),但该方法调用(它本身也试图打开文件)需要写入访问权限,但失败了。

如上所示,关键是使用相同的$file(FileStream实例,用于专门打开文件以更新文件。

还要注意,在$xml.Save($path)之前调用$file.Close()而不是解决方案,因为这引入了一个竞争条件,在这两个语句之间的时间内,另一个进程可以打开文件。

最新更新