我希望将三个不同的任务外包给 powershell 中的文件系统观察者。我将代码全部设置为初始化两个观察者并每十秒检查一次以确保它们正在运行。然而,他们执行的任务分别持续不到一分钟和 5 分钟。我希望外包给观察者的第三个任务大约需要一个小时。我担心如果我让它们全部同时运行,那么如果第三个观察者正在执行其更改操作,那么前两个应该观察的任务根本不会完成。有没有办法实现或运行它们,以便可以并行执行更改操作?
最佳答案
您可以使用 Start-ThreadJob
cmdlet 以并行运行文件监视任务。
Start-ThreadJob
附带 ThreadJob
module并为基于子进程的常规后台作业提供了一种轻量级、基于线程的替代方案。它带有 PowerShell [Core] v6+,在 Windows PowerShell 中可以按需安装,例如
Install-Module ThreadJob -Scope CurrentUser
.在大多数情况下,线程作业是更好的选择,无论是性能还是类型保真度 - 请参阅 this answer 的底部部分为什么。
以下自包含示例代码:
笔记:
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/