如何在Foreach-Object -Parallel外部获取循环内设置的变量值
在PowerShell中通过Foreach-Object -Parallel获取循环内变量值的问题
当使用Foreach-Object -Parallel结合While循环时,无法在循环外部获取内部设置的变量值。比如以下简化脚本中,最终Write-Host输出的$limitReached无值,但预期应为TRUE。
原脚本示例
$retries = 2 $secondsDelay = 2 $a = "house" 1..2 | ForEach-Object -Parallel { $retryCount = 0 $ready = $false while ($ready -ne $true) { if ($a -eq "hello") { Write-Output ("a is equal ""hello""".) $ready = $true } else { Write-Output ("a is not equal ""hello""".) if ($retryCount -ge $using:retries) { Write-Output ("a is STILL not equal ""hello""".) $global:limitReached = $true Write-Host "Checking - limitReached value is [$limitReached]" return } else { Write-Output ("let's give more runs.")`n Start-Sleep $using:secondsDelay $retryCount++ } } } } Write-Host "I need this value - limitReached here is [$global:limitReached]"
结果对比
- 预期结果:
I need this value - limitReached here is [TRUE] - 实际结果:输出的
$global:limitReached无值(显示为空)
问题原因
Foreach-Object -Parallel会启动独立的PowerShell后台进程执行并行任务,进程间的全局变量是隔离的——你在并行块内设置的$global:limitReached只属于当前子进程,主进程无法读取到这个值。另外原脚本中$a变量在并行块内未通过using:引用,实际是未定义状态,这也是一个隐性问题。
解决方案
方法1:通过输出值收集结果
并行块内直接返回标记值,主进程收集所有并行任务的输出后判断状态:
$retries = 2 $secondsDelay = 2 $a = "house" # 收集并行任务的输出结果 $results = 1..2 | ForEach-Object -Parallel { $retryCount = 0 $ready = $false while ($ready -ne $true) { if ($using:a -eq "hello") # 用using:引用外部变量 { Write-Output "a is equal `"hello`"" $ready = $true } else { Write-Output "a is not equal `"hello`"" if ($retryCount -ge $using:retries) { Write-Output "a is STILL not equal `"hello`"" # 返回标记值 return $true } else { Write-Output "let's give more runs.`n" Start-Sleep $using:secondsDelay $retryCount++ } } } } # 判断是否有任务触发重试上限 $limitReached = $results -contains $true Write-Host "I need this value - limitReached here is [$limitReached]"
方法2:使用线程安全集合共享状态
如果需要共享更复杂的状态,可使用ConcurrentBag这类线程安全集合,避免多进程竞态问题:
$retries = 2 $secondsDelay = 2 $a = "house" # 创建线程安全集合 $statusBag = [System.Collections.Concurrent.ConcurrentBag[bool]]::new() 1..2 | ForEach-Object -Parallel { $retryCount = 0 $ready = $false $localBag = $using:statusBag while ($ready -ne $true) { if ($using:a -eq "hello") { Write-Output "a is equal `"hello`"" $ready = $true } else { Write-Output "a is not equal `"hello`"" if ($retryCount -ge $using:retries) { Write-Output "a is STILL not equal `"hello`"" # 向线程安全集合添加标记 $localBag.Add($true) return } else { Write-Output "let's give more runs.`n" Start-Sleep $using:secondsDelay $retryCount++ } } } } # 检查集合中是否存在触发上限的标记 $limitReached = $statusBag -contains $true Write-Host "I need this value - limitReached here is [$limitReached]"
内容的提问来源于stack exchange,提问作者Mund
相关产品推荐
相关产品推荐

