powershell - 多个 io.filesystemwatchers 并行

标签 powershell parallel-processing filesystemwatcher

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

最佳答案

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

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

  • 以下自包含示例代码:
  • 使用线程作业并行运行 2 个不同的文件监控和处理任务,
  • 既不会阻止对方也不会阻止调用者。

  • 笔记:
  • 每个任务创建自己的 System.IO.FileSystemWatcher 下面的代码中的实例,尽管创建太多可能会给系统带来很大的负载,可能导致事件丢失。
    另一种方法是共享实例,例如在调用者的上下文中创建一个实例,线程作业可以访问该实例(参见下面源代码中的注释)。
  • [这部分是推测性的;如果我弄错了,请告诉我们] 直接 FileSystemWatcher .NET 事件处理程序委托(delegate)应保持简短,但通过 Register-ObjectEvent 创建的事件作业从 PowerShell 订阅事件在 PowerShell 端对事件进行排队,然后 PowerShell 将其分派(dispatch)到 -Action脚本 block ,因此这些 block 在下面执行长时间运行的操作不应该是一个直接的问题(尽管这些任务可能需要很长时间才能 catch )。
  • # 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
    .................
    

    关于powershell - 多个 io.filesystemwatchers 并行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60134126/

    相关文章:

    powershell - Where-Object 不过滤

    arrays - OpenCL 2D 工作组尺寸

    file - 如何在Scala中并行写入进程文件和写入结果?

    c# - FileSystemWatcher 触发后 - 线程池还是专用线程?

    node.js - 在 Node.js 中,是否可以判断被监视的文件被移动到了哪里?

    powershell - 在PowerShell代码中测试文件夹是否是连接点?

    修复启动缓慢的 PowerShell 步骤

    csv - 如何使用 PowerShell 读取 zip 文件中的 csv 文件的内容

    python - 如何在 Slurm 集群的多个节点上运行 MPI Python 脚本?错误 : Warning: can't run 1 processes on 2 nodes, 将 nnodes 设置为 1

    c# - FileSystemWatcher:忽略自己进程所做的更改