多个io.filesystemwatchers并行

Multiple io.filesystemwatchers in parallel

我希望将三个不同的任务外包给 powershell 中的文件系统观察器。我已将所有代码设置为初始化两个观察者并每十秒检查一次以确保它们 运行ning。然而,他们执行的任务分别持续不到一分钟和 5 分钟。我希望外包给观察者的第三项任务大约需要一个小时。我担心如果我同时拥有所有这些 运行ning,那么如果第三个观察者正在执行其更改操作,前两个应该观察的任务将根本无法完成。有没有一种方法可以实现或 运行 它们以便可以并行执行更改操作?

您可以使用 Start-ThreadJob cmdlet 来并行 运行 您的文件监视任务。

  • Start-ThreadJobThreadJob module 一起提供,并提供了一个轻量级的、基于 线程 的替代基于子进程的常规后台作业。 它随 PowerShell [Core] v6+ 一起提供,在 Windows 中,PowerShell 可以按需安装,例如 Install-Module ThreadJob -Scope CurrentUser。 在大多数情况下,线程作业是更好的选择,无论是性能还是类型保真度 - 请参阅 的底部部分了解原因。

以下自包含示例代码:

  • 使用线程作业 运行 2 个不同的文件监控和并行处理任务,
  • 既不会阻止对方也不会阻止呼叫者。

注:

  • 每个任务在下面的代码中创建自己的 System.IO.FileSystemWatcher 实例,尽管创建太多实例会给系统带来很大的负载,可能导致事件丢失。 另一种方法是 share 实例,例如在调用者的上下文中创建一个线程作业可以访问的实例(请参阅下面源代码中的注释)。

  • [这部分是推测性的;如果我弄错了请告诉我们] Direct FileSystemWatcher .NET 事件处理程序委托应保持简短,但通过Register-ObjectEvent queues PowerShell 端的事件创建的事件作业,然后 PowerShell 将其分派给 -Action 脚本块,以便这些块执行 long-[=下面的 66=]ning 操作不应该立即 关注(尽管任务可能需要很长时间才能赶上)。

# Make sure that the ThreadJob module is available.
# In Windows PowerShell, it must be installed first.
# In PowerShell [Core], it is available by default.
Import-Module ThreadJob -ea Stop

try {

  # Use the system's temp folder in this example.
  $dir = (Get-Item -EA Ignore temp:).FullName; if (-not $dir) { $dir = $env:TEMP }

  # Define the tasks as an array of custom objects that specify the dir.
  # and file name pattern to monitor as well as the action script block to 
  # handle the events.
  $tasks = # array of custom objects to describe the 
    [pscustomobject] @{
      DirToMonitor = $dir
      FileNamePattern = '*.tmp1'
      Action = {
        # Print status info containing the event data to the host, synchronously.
        Write-Host -NoNewLine "`nINFO: Event 1 raised:`n$($EventArgs | Format-List | Out-String)"
        # Sleep to simulate blocking the thread with a long-running  task.
        Write-Host "INFO: Event 1: Working for 4 secs."
        Start-Sleep 4
        # Create output, which Receive-Job can collect.
        "`nEvent 1 output: " + $EventArgs.Name
      }
    },
    [pscustomobject] @{
      DirToMonitor = $dir
      FileNamePattern = '*.tmp2'
      Action = {
        # Print status info containing the event data to the host, synchronously
        Write-Host -NoNewLine "`nINFO: Event 2 raised:`n$($EventArgs | Format-List | Out-String)"
        # Sleep to simulate blocking the thread with a long-running  task.
        Write-Host "INFO: Event 2: Working for 2 secs"
        Start-Sleep 2
        # Create output, which Receive-Job can collect.
        "`nEvent 2 output: " + $EventArgs.Name
      }  
    } 

  # Start a separate thread job for each action task.
  $threadJobs = $tasks | ForEach-Object {

    Start-ThreadJob -ArgumentList $_ {

      param([pscustomobject] $task)

      # Create and initialize a thread-specific watcher.
      # Note: To keep system load low, it's generally better to use a *shared* 
      #       watcher, if feasible. You can define it in the caller's scope
      #       and access here via $using:watcher
      $watcher = [System.IO.FileSystemWatcher] [ordered] @{
        Path   = $task.DirToMonitor
        Filter = $task.FileNamePattern
        EnableRaisingEvents = $true # start watching.
      }

      # Subscribe to the watcher's Created events, which returns an event job.
      # This indefinitely running job receives the output from the -Action script
      # block whenever the latter is called after an event fires.
      $eventJob = Register-ObjectEvent -ea stop $watcher Created -Action $task.Action

      Write-Host "`nINFO: Watching $($task.DirToMonitor) for creation of $($task.FileNamePattern) files..."

      # Indefinitely wait for output from the action blocks and relay it.
      try {
        while ($true) {
          Receive-Job $eventJob
          Start-Sleep -Milliseconds 500  # sleep a little
        }
      }
      finally { 
         # !! This doesn't print, presumably because this is killed by the
         # !! *caller* being killed, which then doesn't relay the output anymore.
        Write-Host "Cleaning up thread for task $($task.FileNamePattern)..."
        # Dispose of the watcher.
        $watcher.Dispose()
        # Remove the event job (and with it the event subscription).
        $eventJob | Remove-Job -Force 
      }

    }

  }  

  $sampleFilesCreated = $false
  $sampleFiles = foreach ($task in $tasks) { Join-Path $task.DirToMonitor ("tmp_$PID" + ($task.FileNamePattern -replace '\*')) }

  Write-Host "Starting tasks...`nUse Ctrl-C to stop."

  # Indefinitely wait for and display output from the thread jobs.
  # Use Ctrl+C to stop.
  $dtStart = [datetime]::UtcNow
  while ($true) {

    # Receive thread job output, if any.
    $threadJobs | Receive-Job

    # Sleep a little.
    Write-Host . -NoNewline
    Start-Sleep -Milliseconds 500

    # A good while after startup, create sample files that trigger all tasks.
    # NOTE: The delay must be long enough for the task event handlers to already be
    #       in place. How long that takes can vary.
    #       Watch the status output to make sure the files are created
    #       *after* the event handlers became active.
    #       If not, increase the delay or create files manually once
    #       the event handlers are in place.
    if (-not $sampleFilesCreated -and ([datetime]::UtcNow - $dtStart).TotalSeconds -ge 10) {
      Write-Host
      foreach ($sampleFile in $sampleFiles) {
        Write-Host "INFO: Creating sample file $sampleFile..."
        $null > $sampleFile
      }
      $sampleFilesCreated = $true
    }

  }

}
finally {
  # Clean up.
  # Clean up the thread jobs.
  Remove-Job -Force $threadJobs
  # Remove the temp. sample files
  Remove-Item -ea Ignore $sampleFiles
}

上面创建的输出如下(来自 macOS 机器的示例):

Starting tasks...
Use Ctrl-C to stop.
.
INFO: Watching /var/folders/19/0lxcl7hd63d6fqd813glqppc0000gn/T/ for creation of *.tmp1 files...

INFO: Watching /var/folders/19/0lxcl7hd63d6fqd813glqppc0000gn/T/ for creation of *.tmp2 files...
.........
INFO: Creating sample file /var/folders/19/0lxcl7hd63d6fqd813glqppc0000gn/T/tmp_91418.tmp1...
INFO: Creating sample file /var/folders/19/0lxcl7hd63d6fqd813glqppc0000gn/T/tmp_91418.tmp2...
.
INFO: Event 1 raised:

ChangeType : Created
FullPath   : /var/folders/19/0lxcl7hd63d6fqd813glqppc0000gn/T/tmp_91418.tmp1
Name       : tmp_91418.tmp1


INFO: Event 1: Working for 4 secs.

INFO: Event 2 raised:

ChangeType : Created
FullPath   : /var/folders/19/0lxcl7hd63d6fqd813glqppc0000gn/T/tmp_91418.tmp2
Name       : tmp_91418.tmp2


INFO: Event 2: Working for 2 secs
....
Event 2 output: tmp_91418.tmp2
....
Event 1 output: tmp_91418.tmp1
.................