第8天:任务计划、后台作业与多线程(Runspace)
—— 实现本地可运行的并发自动化
💡 核心目标:掌握从“单线程脚本”到“高性能并行任务”的跃迁
一、为什么需要并发?—— 单线程的瓶颈
# 单线程:依次 Ping 5 个地址(耗时 ~5 秒)
"google.com", "github.com", "microsoft.com", "stackoverflow.com", "localhost" | ForEach-Object {
$start = Get-Date
Test-Connection $_ -Count 1 -ErrorAction SilentlyContinue | Out-Null
Write-Host "$_ 耗时: $((Get-Date) - $start).TotalSeconds 秒"
}
🕒 总耗时 ≈ 各任务之和 → 无法满足批量运维需求
二、方案1:后台作业(Job)—— 简单但低效
✅ 优点:语法简单,完全隔离
❌ 缺点:每个 Job 启动新进程,内存/CPU 开销大
🔧 实践:用 Start-Job 并行 Ping
$servers = "google.com", "github.com", "microsoft.com", "localhost"
# 启动作业
$jobs = foreach ($server in $servers) {
Start-Job -ScriptBlock {
param($s)
$result = Test-Connection $s -Count 1 -ErrorAction SilentlyContinue
[PSCustomObject]@{
Host = $s
Online = [bool]$result
Time = if ($result) { $result.ResponseTime } else { $null }
}
} -ArgumentList $server
}
# 等待并收集结果
$results = $jobs | Wait-Job | Receive-Job
# 清理(重要!)
$jobs | Remove-Job
# 输出
$results | Sort-Object Time | Format-Table -AutoSize
📊 性能提示:打开任务管理器,你会看到多个
pwsh.exe或powershell.exe进程同时运行。
三、方案2:PowerShell 7+ 原生并行 —— ForEach-Object -Parallel
✅ 优点:语法简洁、同进程多线程、开销小
❌ 缺点:仅限 PowerShell 7+,变量需用 $using: 引入
🔧 实践:并行检查本地端口是否开放
💡 💡 💡 💡 注意:需要安装PowerShell7,安装后可能需要运行pwsh来切换PowerShell版本,
查看版本方法 $PSVersionTable💡 💡 💡 💡
# 检查本机常用端口
$ports = 80, 443, 3389, 1433, 22, 3306
$portCheckScript = {
param($port)
$tcp = New-Object System.Net.Sockets.TcpClient
$connect = $tcp.BeginConnect("127.0.0.1", $port, $null, $null)
$wait = $connect.AsyncWaitHandle.WaitOne(1000, $false) # 1秒超时
$open = $false
if ($wait) {
try { $tcp.EndConnect($connect); $open = $true } catch {}
}
$tcp.Close()
[PSCustomObject]@{ Port = $port; Open = $open }
}
# 并行执行(最多3线程)
$ports | ForEach-Object -Parallel $portCheckScript -ThrottleLimit 3 |
Where-Object Open |
Format-Table -AutoSize
✅ 输出示例(若 RDP 开启):
Port Open ---- ---- 3389 True
💡 注意:若在 PowerShell 5.1 中运行,会报错“不支持 -Parallel”。
四、方案3:RunspacePool —— 高性能通用方案(兼容 PS 5.1+)
✅ 优点:同进程多线程、低开销、完全控制、跨版本兼容
🔧 封装为可复用函数 Invoke-Parallel
function Invoke-Parallel {
param(
[Parameter(Mandatory)] [object[]]$InputObject,
[Parameter(Mandatory)] [scriptblock]$ScriptBlock,
[int]$ThrottleLimit = 5
)
$runspacePool = [runspacefactory]::CreateRunspacePool(1, $ThrottleLimit)
$runspacePool.Open()
$jobs = @()
foreach ($item in $InputObject) {
$ps = [powershell]::Create().AddScript($ScriptBlock).AddArgument($item)
$ps.RunspacePool = $runspacePool
$jobs += [PSCustomObject]@{
PowerShell = $ps
Handle = $ps.BeginInvoke()
}
}
# 收集结果
$results = foreach ($job in $jobs) {
$job.PowerShell.EndInvoke($job.Handle)
$job.PowerShell.Dispose()
}
$runspacePool.Close()
$runspacePool.Dispose()
return $results
}
🔧 实践:并行获取多个进程的 CPU 使用率(模拟耗时操作)
# 获取前5个高内存进程
$processes = Get-Process | Sort-Object WorkingSet -Descending | Select-Object -First 5 -ExpandProperty Name
# 模拟“获取详细指标”的耗时操作
$metricScript = {
param($procName)
Start-Sleep -Milliseconds (Get-Random -Min 200 -Max 800) # 模拟延迟
$proc = Get-Process -Name $procName -ErrorAction SilentlyContinue
if ($proc) {
[PSCustomObject]@{
Process = $procName
CPU = [math]::Round(($proc.CPU | Measure-Object -Sum).Sum, 2)
MemoryMB = [math]::Round(($proc.WorkingSet64 | Measure-Object -Sum).Sum / 1MB, 2)
}
}
}
# 并行执行(4线程)
Invoke-Parallel -InputObject $processes -ScriptBlock $metricScript -ThrottleLimit 4 |
Sort-Object CPU -Descending |
Format-Table -AutoSize
📊 性能对比:
- 单线程:~2–4 秒
- Runspace(4线程):~0.8 秒
- Job(4进程):~1.5 秒 + 更高内存占用
五、任务计划:让脚本自动运行
🎯 场景:每天凌晨清理临时文件
# 创建清理脚本
$scriptPath = "C:\Scripts\CleanupTemp.ps1"
@'
Get-ChildItem $env:TEMP -Recurse -Force |
Where-Object LastWriteTime -lt (Get-Date).AddDays(-7) |
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
'@ | Set-Content -Path $scriptPath
# 注册任务(以 SYSTEM 身份每天 2:00 AM 运行)
$action = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "-File `"$scriptPath`""
$trigger = New-ScheduledTaskTrigger -Daily -At "2:00AM"
$principal = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount
Register-ScheduledTask -TaskName "CleanupTempFiles" -Action $action -Trigger $trigger -Principal $principal
✅ 验证:打开 任务计划程序 → 查看“任务计划程序库”
六、三种并发方式对比总结
| 方式 | 进程模型 | 内存开销 | 兼容性 | 适用场景 |
|---|---|---|---|---|
Start-Job | 多进程 | ❌ 高(每个 Job ≈ 100MB+) | ✅ PS 3+ | 长时间运行、需完全隔离的任务 |
ForEach-Object -Parallel | 单进程多线程 | ✅ 低 | ❌ 仅 PS 7+ | 快速开发、轻量级并行管道 |
RunspacePool | 单进程多线程 | ✅ 低 | ✅ PS 3+(需 .NET) | 高性能批量任务、企业脚本 |
📌 推荐策略:
- 日常脚本(PS 7+)→ 优先用
-Parallel- 企业级/兼容性要求高 → 用
Invoke-Parallel(Runspace 封装)- 需要独立环境(如不同用户上下文)→ 用
Start-Job
七、今日重点总结
- ✅ 并发 ≠ 多进程:Runspace 是更高效的多线程方案
- ✅ 所有异步操作后必须清理资源(
Remove-Job/Dispose()) - ✅ 任务计划用
Register-ScheduledTask,而非已废弃的ScheduledJob - ✅ 本地即可练习并发:用
Start-Sleep、Test-Connection、Get-Process模拟耗时操作
🏁 课后作业(全部本地可运行)
- 使用
Start-Job:启动3个后台作业,分别休眠3、5、7秒,记录总耗时 vs 单线程。 - 使用
ForEach-Object -Parallel(PS 7+):并行计算 1 到 10 的平方,并按输入顺序输出。 - 使用
Invoke-Parallel函数:并行检查本机80, 443, 3389, 1433, 22, 21端口是否开放,输出开放端口列表。
💡 提示:端口检查可复用上文
$portCheckScript。
3万+

被折叠的 条评论
为什么被折叠?



