Параллельный доступ для чтения / записи к XML - PullRequest
0 голосов
/ 15 декабря 2018

Я пытаюсь внедрить обновления информации в файл 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?Кажется, что это будет распространенный сценарий, поэтому должен быть лучший способ сделать это, даже если нет собственного подхода к командлетам.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, который у меня есть, с двумя фиктивными элементами журнала, прочитать его, добавить другой элемент журнала (с идентификатором, итерацией, временем ожидания и временемпечать) и повторить 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 голосов
/ 16 декабря 2018

На заметку общего характера: файлы не оптимизированы для одновременного доступа, как базы данных , поэтому, если вам нужен параллельный доступ с некоторой изощренностью, вам нужно будет свернутьваш собственный.

Этот ответ на тесно связанный вопрос демонстрирует использование отдельного файла блокировки (сторожевой файл) для управления параллелизмом с минимальным нарушением .

Однако вы можете упростить подход и избавить от необходимости файл блокировки, если вы готовыпоставить эксклюзивную блокировку на файл на всю продолжительность чтения, изменения и сохранения изменений .

В отличие от блокировки,файловый подход позволяет читать и готовить изменения одновременно с другими процессами , читая файл, и требует только исключительной блокировки для фактического акта перезаписи / замены файла.

Однако при обоих подходахПериод из требуется исключительная блокировка файла, чтобы предотвратить непредсказуемость чтения читателями из файла во время его перезаписи.

При этом вам все еще нужно сотрудничество свсе участвующие процессы :

  • Писатели должны иметь дело с (временной) неспособностью открыть файл исключительно, а именно с другими процессами (читателями или писателями)

  • Аналогично, читатели должны быть готовы обработать (временную) невозможность открыть файл (пока он обновляется записывающим устройством).

Ключ для:

  • Открыть файл в режиме общего доступа к файлам None (т. Е. Запретить другим процессам использовать тот жефайл, пока он у вас открыт), и держать его открытым до завершения обновления.Это гарантирует, что операция является атомарной с точки зрения межпроцессного взаимодействия.

  • Использование только экземпляра FileStream, возвращенного [System.IO.File]::Open(), для чтения и записи вфайл (вызов командлетов или методов .NET, таких как 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), пока $file все еще открыт, но этот вызов метода - который сам по себепытается открыть файл тоже - требует запись доступ, который не удается.

Как показано выше, ключ должен использовать тот же $file (FileStreamЭкземпляр используется для открытия файла исключительно для обновления файла.

Также обратите внимание, что вызов $file.Close() простопрежде чем $xml.Save($path) является , а не решением, потому что это вводит условие состязания, когда другой процесс может открыть файл за время между двумя операторами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...