You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PowerShell中如何用Runspace池实现ArrayList动态增项并发递归处理

问题说明

现有遍历ArrayList的串行for循环会在执行过程中向列表动态新增项,且需要迭代处理所有新增项,串行实现运行正常,共得到12个数组元素,符合预期。尝试使用RunspacePool改造为并发执行时,使用线程安全的同步ArrayList存储数据,无处理延迟时结果符合预期,但启用Start-Sleep 1模拟实际业务延迟后,初始for循环会在运行空间完成新增项操作前退出,仅初始的5个元素被提交到Runspace池,最终仅得到10个元素,结果不符合预期。

串行实现参考代码

$array = [System.Collections.ArrayList]@(1, 2, 3, 4, 5)
Write-Host "Number of items in array before loop: $($array.Count)"
for ($i = 0; $i -lt $array.Count; $i++) {
    Write-Host "Counter: $i`tArray: $array"
    if ($array[$i] -in @(1, 2, 3, 4, 5)) {
        $array.Add($array[$i] + 3) | Out-Null
    }
}
Write-Host "Array: $array"
Write-Host "Number of items in array after loop: $($array.Count)"

存在问题的并发实现代码

$pool = [RunspaceFactory]::CreateRunspacePool(1, 10)
$pool.Open()
$runspaces = @()

$scriptblock = {
    Param ($i, $array)
    # Start-Sleep 1 # <------ 启用后输出结果会出现明显偏差
    Write-Output "$i value: $array"
    if ($i -in @(1, 2, 3, 4, 5)) {
        $array.Add($i + 3) | Out-Null
    }
}

$array = [System.Collections.ArrayList]::Synchronized(([System.Collections.ArrayList]$(1, 2, 3, 4, 5)))
Write-Host "Number of items in array before loop: $($array.Count)"
for ($i = 0; $i -lt $array.Count; $i++) {
    $runspace = [PowerShell]::Create().AddScript($scriptblock).AddArgument($array[$i]).AddArgument($array)
    $runspace.RunspacePool = $pool
    $runspaces += [PSCustomObject]@{ Pipe = $runspace; Status = $runspace.BeginInvoke() }
}

while ($runspaces.Status -ne $null) {
    $completed = $runspaces | Where-Object { $_.Status.IsCompleted -eq $true }
    foreach ($runspace in $completed) {
        $runspace.Pipe.EndInvoke($runspace.Status)
        $runspace.Status = $null
    }
}
Write-Host "array: $array"
Write-Host "Number of items in array after loop: $($array.Count)"
$pool.Close()
$pool.Dispose()
问题根因

原并发实现采用一次性固定遍历的逻辑提交任务,循环条件$i -lt $array.Count仅在每次迭代瞬间读取数组长度,主线程提交完初始5个元素对应的任务后就会退出提交循环,不会等待已提交任务执行产生新元素,后续Runspace新增的元素不会被提交处理,最终导致结果缺失。

解决方案

将一次性遍历提交的逻辑改为持续检测待处理元素+跟踪运行中任务的消费模式,直到所有元素都被提交处理、所有运行中任务执行完成且无新元素产生时再退出流程,核心实现要点:

  • 保留线程安全的Synchronized ArrayList存储数据,避免多线程下集合修改异常
  • 新增线程安全的字典结构记录已提交处理的元素索引,避免同一元素被重复提交
  • 常驻循环持续检测数组新索引,发现未提交的元素就创建Runspace任务提交到池
  • 退出条件同时校验「无正在运行的任务」「所有现有元素都已提交处理」两个规则
  • 增加短休眠避免CPU空转占用过高资源

修正后的可运行代码如下:

$pool = [RunspaceFactory]::CreateRunspacePool(1, 10)
$pool.Open()
$runspaces = @()
# 线程安全字典,记录已提交处理的元素索引,避免重复提交
$submittedIndex = [System.Collections.Concurrent.ConcurrentDictionary[int, byte]]::new()

$scriptblock = {
    Param ($i, $array)
    Start-Sleep 1 # 模拟业务处理延迟,开启后逻辑依然正常
    Write-Output "Processing value: $i`tCurrent array: $array"
    if ($i -in @(1, 2, 3, 4, 5)) {
        $array.Add($i + 3) | Out-Null
    }
}

# 初始化线程安全数组
$array = [System.Collections.ArrayList]::Synchronized(([System.Collections.ArrayList]$(1,2,3,4,5)))
Write-Host "Number of items in array before loop: $($array.Count)"

while ($true) {
    # 提交所有未处理的索引对应任务
    for ($i = 0; $i -lt $array.Count; $i++) {
        if (-not $submittedIndex.ContainsKey($i)) {
            # 原子操作标记索引为已提交,防止多线程下重复提交
            if ($submittedIndex.TryAdd($i, 0)) {
                $runspace = [PowerShell]::Create().AddScript($scriptblock).AddArgument($array[$i]).AddArgument($array)
                $runspace.RunspacePool = $pool
                $runspaces += [PSCustomObject]@{ Pipe = $runspace; Status = $runspace.BeginInvoke() }
            }
        }
    }

    # 回收已完成的Runspace资源
    $completed = $runspaces | Where-Object { $_.Status.IsCompleted -eq $true }
    foreach ($runspace in $completed) {
        $runspace.Pipe.EndInvoke($runspace.Status)
        $runspace.Pipe.Dispose()
        $runspace.Status = $null
    }
    # 移除已完成的任务跟踪记录
    $runspaces = $runspaces | Where-Object { $_.Status -ne $null }

    # 满足退出条件:无运行中任务 + 所有现有元素都已提交处理
    if ($runspaces.Count -eq 0 -and $submittedIndex.Count -eq $array.Count) {
        break
    }

    # 短暂休眠降低CPU占用
    Start-Sleep -Milliseconds 100
}

Write-Host "Final array: $array"
Write-Host "Number of items in array after loop: $($array.Count)"
$pool.Close()
$pool.Dispose()

该实现支持任意层级的动态新增元素递归处理,无论业务处理延迟多长,最终结果都会和串行版本一致,得到12个元素的正确输出。


内容的提问来源于stack exchange,提问作者Suraj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 23:15:47