【问题标题】:Powershell StreamReader - how to wait for a new file to be readablePowershell StreamReader - 如何等待新文件可读
【发布时间】:2020-01-14 14:43:08
【问题描述】:

我的脚本通常假设存在一个 *.txt 文件,并设置了帮助它更好地运行的设置。但是,如果脚本不存在,它会创建一个本地文件来保存这些设置。我意识到没有逻辑需要然后读取此文件,但我想了解为什么我不能。

[void][System.IO.File]::Create($PSFileName)
$ReadPS = New-Object System.IO.StreamReader($PSFileName)

在脚本可能(很少)创建文件后,它会立即尝试读取它,这会产生以下错误:New-Object : Exception calling ".ctor" with "1" argument(s): "The process cannot access the file 'C:\Temp\MyFile.txt' because it is being used by another process."

所以我必须等待文件可用,对吗?然而,简单的 5s 开始睡眠是行不通的。但是,如果我用 try-catch 将它包装在一个循环中,它每次都会在几分之一秒内工作:

[void][System.IO.File]::Create($PSFileName)
$RCount = 0                                                             # if new file created, sometimes it takes a while for the lock to be released. 
Do{
    try{
        $ReadPS = New-Object System.IO.StreamReader($PSFileName)
        $RCount+=100
    }catch{                                                             # if error encountered whilst setting up StreamReader, try again up to 100 times.
        $RCount++
        Start-Sleep -Milliseconds 1                                     # Wait long enough for the process to complete. 50ms seems to be the sweet spot for fewest loops at fastest performance
    }
}Until($RCount -ge 100)
$ReadPS.Close()
$ReadPS.Dispose()

这太令人费解了。为什么文件会在任意时间保持锁定状态,而我等待的时间似乎越长?我可以在文件创建和 StreamReader 之间调整或添加什么以确保文件可用吗?

【问题讨论】:

  • 如果我运行您的创建代码,然后在该文件上运行Get-Content,我会收到一条红色错误文本,上面写着Get-Content : The process cannot access the file 'C:\Temp\Testing.txt' because it is being used by another process。如果我连续进行 3 次调用,则第 1 次失败,但第 2 次和第 3 次显示没有错误。如果我在第一次 G-C 调用之前添加Start-Sleep,我会收到一个错误。 ///// 我怀疑这是因为整个脚本停止了 5 秒......并且文件句柄在脚本停止时保持打开状态。这似乎与您的 try/catch 解决方案一致......
  • File.Create 生成一个由FileStream 封装的打开文件句柄。如果您不处置它,那将导致锁定冲突(尽管您,原始进程打开了它)。使用[System.IO.File]::Create($PSFileName).Dispose(),或(更高级一点)$null | Out-File $PSFileName -Encoding ascii
  • 另一种供您批准的选择,以防Out-File 有点太晦涩:[System.IO.File]::WriteAllBytes($PSFileName, @())。请注意,如果文件已经存在,this 和Out-File 都会截断该文件;如果这是不可取的,请使用-AppendOut-FileAppendAllText($PSFileName, "")File (奇怪的是,没有File.AppendAllBytes 方法)。
  • 愚蠢的问题:为什么我们需要在已知的空文件上使用 StreamReader?我想任何后续答案都可以在 System.IO 下的某处使用写入功能......同样容易。只是想我会指出来......
  • @Steven - 基本上,我花了一段时间重构我的所有代码,以最符合逻辑的“ifs”和“whiles”顺序运行,最后,99%它获取现有配置文件的时间。作为最后的冗余步骤,如果无法获取该文件,我将创建该文件。在所有这些工作之后,无论它是否是一个空的配置文件,运行相同的文件读取步骤都会非常好。我可以添加额外的逻辑来省略该步骤或确保它成功发生,但“感觉”明智的做法是找出它被锁定的原因并避免错误。

标签: powershell file-handling


【解决方案1】:

正如 cmets 中已经提到的,您使用的方法确实会在文件上创建一个锁定,直到您调用 close / dispose 方法或 powershell 会话结束。

这就是为什么您等待的时间越长,会话保持打开的时间越长,文件锁定的时间就越长。

我建议您只使用New-Item,这是 Powershell 的原生方式。

虽然您正在创建 StreamReader 对象,但不要忘记在结束后关闭/处置该对象。

New-Item -Path $PSFileName -ItemType File
$ReadPS = New-Object System.IO.StreamReader($PSFileName)

#Stuff

$ReadPS.Close()
$ReadPS.Dispose()

最后,如果出于某种原因您仍想使用[System.IO.File]::Create($PSFileName),您还需要调用close 方法来释放锁。

【讨论】:

  • 更准确地说:句柄一直保留到对象被释放,这通常发生在下一次垃圾回收时,因为在创建它的语句之后对象不再可访问。发生这种情况的时间是不确定的;特别是,句柄有可能在脚本或会话结束之前被释放。这种不确定性只会让诊断变得更加困难。
  • 好吧,我为不记得 New-Item 是一件事而感到尴尬。如果现有文件不应该成为问题,您可能希望包含可以使用-ErrorAction Ignore 的提示。
  • 啊,谢谢@JeroenMostert 我编辑了我的答案,所以指定dispose。由于我大量使用 Powershell,我的 .net 变得有点生疏了。 .
  • 所有答案都非常有帮助,但这绝对是最好的——感谢@SagePourpre 和@Jeroen。作为一个相对较新的 Powershell 用户,我陷入了使用 [System.IO.File] 的陷阱,因为我在代码的其他地方使用了它的 Exists() 函数,而且我之前从未明确请求创建文件。该解决方案在不修改其他代码的情况下实现了最佳拟合,但为了完整起见,我要补充一点,| Out-Null 在文件创建期间抑制用户反馈也很有用。
【解决方案2】:

您只需关闭文件句柄即可。试试:

$fh = [System.IO.File]::Create($PSFileName)
[void]$fh.Close()
[void]$fh.Dispose()
$ReadPS = New-Object System.IO.StreamReader($PSFileName)

【讨论】:

    【解决方案3】:

    Create 方法返回一个 FileStream 对象。由于 StreamReader 是从 Stream 派生的,我的解决方案是重铸为流阅读器。几乎是单线……:

    $PSFileName = 'c:\temp\testfile.txt'
    $Stream = [System.IO.StreamReader][System.IO.File]::Create($PSFileName)
    

    或者,来自 Jeroen Mostert 的建议:

    $PSFileName = 'c:\temp\testfile.txt'
    $Stream = [System.IO.StreamReader]::New( [System.IO.File]::Create($PSFileName) )
    

    您不必担心使用这种方法进行垃圾收集,因为结果对象引用了变量...

    老实说,我对此不太确定,我相信 FileStream 对象可以直接用于读取和写入,但我对 StreamReader 和 Writer 对象不太熟悉,所以如果是我,我会做重铸,这样我就可以继续前进,但稍后会进一步研究。

    另外,如果您使用其他方法,我会使用 .CLose() 而不是 .Dispose()。我对 .Net 文档的理解更为透彻,无论如何在内部调用 Dispose...

    【讨论】:

    • 这可行,但似乎不必要地令人困惑——转换调用 StreamReader 构造函数,但不是“真正的”转换。我更喜欢在最新版本的 PowerShell 中使用明确的 [System.IO.StreamReader]::new(...),或者在旧版本中使用 New-Object System.IO.StreamReader (...)
    • 我也更喜欢 ::New() 而不是 New-Object。但我注意到 System.IO.* 类通常对强制转换很友好。也就是说,我在后两者之间没有偏好。无论如何,我会根据你的建议进行编辑......
    • 这是一个我几乎实现的简洁解决方案 - 但由于在我的逻辑中文件创建是少数情况,因此修改更常见的情况以适应此解决方案感觉不对。在这种情况下,用接受的答案替换少数情况更容易,但在任何其他情况下,这都是一种非常整洁的方法,并且会是我的偏好。
    • 使用 .Net 的东西通常是为了获得更好的性能。事实上,我最近写了一篇关于这个的文章。但我同意并实践相同。我不使用它们,除非有需要和很多收获......谢谢!
    • 很高兴知道。总是让我感到困扰,所以很少有人会费心为好的答案和问题投票 - 所以你会注意到我已经对你在其他 Powershell 问题上的一些好的答案投了赞成票,以便对你在这里所做的工作给予适当的认可.祝您度过愉快的一周
    猜你喜欢
    • 1970-01-01
    • 2010-12-17
    • 1970-01-01
    • 2016-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多