对XML的并发读写访问

时间:2018-12-15 17:25:44

标签: xml powershell concurrency

我正在尝试通过同时在多台计算机上运行的多个进程在XML文件中实现信息更新。我的想法是循环10分钟,尝试打开并锁定文件以随机间隔(最多1秒)进行写入。打开并锁定文件后,我将加载所有XML,添加当前计算机的信息,对XML进行排序,然后转售并删除锁定,以便可以打开下一台计算机。问题在于Get-Content不会锁定文件,因此两台计算机可以加载相同的XML,而不是第二台计算机使用第一台计算机的数据加载XML。 我发现this,它提供了一种锁定文件,然后按流读取的方式,但是当我尝试对此进行修改时

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

我得到一个错误,因为文件被锁定。似乎Get-Content不会锁定文件,但是它确实尊重已经存在的锁定。 那么,有没有一种方法可以锁定文件,以便只有计算机锁定才能读写? 也许更重要的是,这是否是正确的方法,还是存在其他一些方法来进行多个XML访问?似乎这是一种常见的情况,因此即使没有本机cmdlet方法,也必须有一些最佳实践方法。 FWIW,我必须重新支持PowerShell 2.0,这无疑限制了我的实现方式。

编辑:嗯,似乎[io.file]位中的第三个参数的“读取”不起作用。 我现在有这个

$path = '\\Px\Support\Px Tools\Resources\jobs.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 '\\Px\Support\Px Tools\Resources\jobs.xml' because it is being used by another process."

我真的是在尝试做1000年前没有做过的事情吗?

好的,根据评论,这是我的位置。我想确保在进行处理时不能(轻松)手动编辑原稿。因此,我已经实现了这一点。 1:查找前哨文件,如果找不到 2:锁定原始文件,使其无法修改 3:将原件复制为前哨文件 4:根据需要修改前哨文件 5:解锁原件 6:将前哨文件复制到原始文件上 7:删除前哨

对我来说,似乎有些难为情,就像有人在解锁原件和复制哨兵之间手动修改原件一样,这种可能性很小。但是,似乎应该有一种可以100%确定性地处理此问题的方法,而且我想不出有没有哨兵文件的方法。

1 个答案:

答案 0 :(得分:1)

一般情况下:文件并没有像数据库那样针对并发访问进行优化,因此,如果您需要一些复杂的并发访问,则需要自己动手。

This answer 到一个密切相关的问题,证明了使用单独的锁定文件 (前哨文件)来管理< strong>最小干扰。

但是,如果愿意为文件添加排他锁,可以简化方法,并不需要锁文件。 整个持续时间,进行读取,修改并保存修改

相比之下,锁定文件方法允许读取和准备与其他进程读取同时进行的修改,并且仅需要排他锁才能进行实际的重写/替换文件。

但是,使用这两种方法时,都需要对文件进行排他锁定,以防止读者在重写文件时从文件中读取文件。

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

  • 作家需要处理(暂时)无法完全打开文件的问题,即在其他进程(读者或作家)正在使用它时。

  • 类似地,必须准备好 readers 来处理(暂时)无法打开文件(由作家更新文件时)的问题。

关键在于:

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

  • 仅使用{em> FileStream返回的[System.IO.File]::Open()实例来读取和写入文件(调用cmdlet或.NET方法,例如{{1} }会失败,因为他们自己将尝试打开-然后被锁定的文件)。


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

System.Xml.XmlDocument.Save()

关于您尝试过的事情

$path = '\\Px\Support\Px Tools\Resources\jobs.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')仍处于打开状态时调用$xml.Save($path),但是该方法调用(它本身也会尝试打开文件)需要 write 访问,但失败。 / p>

如上所示,关键是使用相同的$fileFileStream实例),该实例仅用于打开文件以更新文件。

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