.NET (PowerShell) Одновременное использование файла (блокировка, но при этом разрешается чтение) - PullRequest
0 голосов
/ 16 октября 2018

У меня есть XML-файл, к которому мне нужен параллельный доступ.Мне не требуется одновременная запись, но мне нужно одновременное чтение.Предписанный метод чтения / изменения этого XML-файла заключается в использовании сценария PowerShell , который использует Linq to XML (XElement) для работы с файлом XML.

Идеальный сценарий:

  1. Все пользователи в команде будут запускать этот сценарий PowerShell в начале своего рабочего дня, и он будет работать до конца рабочего дня.
  2. В течение всегоВ рабочий день любой пользователь в команде будет вводить предварительно определенные команды, которые будут запрашивать XML-файл для сбора данных.Каждый раз, когда пользователь запускает один из этих запросов, он открывает файл XML, выполняет запрос, закрывает файл XML, а затем отображает результаты.
  3. Иногда в течение дня любому из этих пользователей потребуетсяизменить файл XML.Пользователь введет заранее определенную команду, указав, какой узел он хочет изменить, затем он введет все данные, которые он хотел бы добавить / изменить.Затем сценарий откроет файл (заблокировав его, чтобы другой пользователь не мог открыть его для чтения / записи), прочитает файл XML в объект XElement, выполнит нужные операции, затем сохранит XElement обратно в файл,и затем снимите блокировку.
  4. В любой момент, когда пользователь изменяет файл (некоторые операции могут быть длительными), любой другой пользователь, который пытается внести изменения , не должен быть в состояниичтобы открыть файл (я поймаю исключение и представлю сообщение типа «Пожалуйста, подождите несколько минут»).Но они должны иметь возможность открывать файл только для чтения для выполнения запросов.

Я видел этот связанный пост 'Использование файла одновременно в C #' , и попытался реализовать это, используя приведенный ниже код.В одном экземпляре PowerShell ISE я выполняю функцию чтения-записи, а затем, находясь в цикле «Ввод имени элемента», я выполняю функцию только для чтения в другом экземпляре 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

Ответы [ 3 ]

0 голосов
/ 16 октября 2018

Может быть, это может помочь.При создании файлового потока есть опция FileShare.Если вы установите его в ReadWrite, несколько процессов могут открыть этот файл

$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()
0 голосов
/ 16 октября 2018

Если вы используете [FileAccess]::Read без явного указания режима общего доступа к файлам, то, если файл уже открыт, его повторное открытие будет успешным, только если он был первоначально открыт в режиме общего доступа к файлам [FileShare]::ReadWrite (даже если выВы запрашиваете только чтение , метод по умолчанию запрашивает запись доступ тоже), тогда как ваша 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 +) пишущий (который, возможно, открыл файл первым).

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

Примечание: Этот ответ к связанному вопросу показывает более простой вариант* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1042 * *вашего файла , затем замените оригинал.

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

Майк Кристиансен (сам ОП) в итоге также сохранил имя пользователя блокирующего пользователя в этом файле, чтобы предоставить обратную связьдругие потенциальные шкафчики.

Когда запрашивается модификация :

  • Продолжайте попытки в цикле до создания файла блокировкиupdating завершается успешно, если файл уже существует.

    • (Re) - создание файла updating сигнализирует другим потенциальным авторам, что изменение началось.Читатели, напротив, могут продолжать читать в этот момент.
  • Создать (временную) копию текущего XML-файла и выполнить там изменения..

    • В итоге сам Майк просто изменил копию файла в памяти , что упрощает задачу (если у вас достаточно памяти для чтения файла в целом).
  • Заменить оригинальный файл измененной копией - используйте цикл повторных попыток, пока копирование поверх / перезапись оригинала не будет успешной, учитывая, что другие читатели могут временно предотвратить удаление (повторное создание) /переписать исходный файл.

  • Удалить файл updating, сообщая другим потенциальным модификаторам, что обновление завершено.

    • Убедитесь, что файл всегда очищен (try .. finally), потому что если он задержится, это заблокирует будущие обновления;вам также может понадобиться механизм тайм-аута, который при ожидании удаления существующего файла в конечном итоге заставляет удалить, если можно предположить, что предыдущий апдейтер вышел из строя.

Что касается чтение доступ:

  • Хотя никаких изменений не происходит, одновременный доступ для чтения должен работать нормально.

  • Пока файл XML заменяется временным копированием / перезаписью, открытие файла для чтения не удастся на время операции копирования файла / операция записи, поэтому вам потребуетсятам тоже повторная петля.


Код, который в конечном итоге использовал Майк, можно найти здесь .

0 голосов
/ 16 октября 2018

Могу я предложить немного другой подход ко всему этому.С PowerShell вы можете сделать следующее, что позволяет избежать проблемы чтения.Моя XML-навигация может быть немного отключена, но она будет работать, как описано здесь .

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)"
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...