The code below isolates the task I am trying to do. Once a number over 3 is checked in $loopMe, I want the foreach-object loop to end across all runspaces and that the value of numberOverLimit is set to true.
$loopMe = [System.Collections.ArrayList]@()
for($index = 0; $index -lt 5; $index++){
$loopMe.Add($index)>$null;
}
$global:numberOverLimit=$false
$addToMe= [System.Collections.Concurrent.ConcurrentBag[psobject]]::new()
$loopMe | Foreach-Object -ThrottleLimit 6 -Parallel{
$localAddToMe=$using:addToMe
Write-Host $_
if($_ -gt 3){
$global:numberOverLimit=$true
break
}
$localAddToMe.Add($_)
}
write-Host $addToMe
Write-Host $numberOverLimit
if($numberOverLimit){
Write-Host "A number was over the limit"
exit
}
else{
Write-Host "All is good"
}
Expected Result
0
1
2
3
3 2 1 0
True
A number was over the limit
Actual Result
0
1
4
2
3
3 2 1 0
False
All is good
Your parallel loop can't see your $global:numberOverLimit
variable and even if it would, it wouldn't be able to update the boolean (a value type). You can only update a reference type from your parallel loop, which is why I'm using Get-Variable
in this example.
Also note, the use of break
in a script block:
Using
break
inside a pipelinebreak
, such as aForEach-Object
script block, not only exits the pipeline, it potentially terminates the entire runspace.
The only built-in method to stop a pipeline early is with Select-Object -First
, you can pipe your parallel loop to it and output anything to stdout to terminate your parallel loop.
Lastly, you should ensure thread safety before updating your PSVariable
instance, for that you need to use some sort of locking mechanism, Monitor.Enter
is the one used in this case. See this answer for other alternatives.
$addToMe = [System.Collections.Concurrent.ConcurrentBag[psobject]]::new()
$numberOverLimit = $false
$psvar = Get-Variable numberOverLimit
0..5 | ForEach-Object -ThrottleLimit 6 -Parallel {
$localAddToMe = $using:addToMe
$psvar = $using:psvar
Write-Host $_
if ($_ -gt 3) {
[System.Threading.Monitor]::Enter($psvar)
$psvar.Value = $true
[System.Threading.Monitor]::Exit($psvar)
return 'something, whatever here'
}
$localAddToMe.Add($_)
} | Select-Object -First 1 | Out-Null
Write-Host $addToMe
Write-Host $numberOverLimit
if ($numberOverLimit) {
Write-Host 'A number was over the limit'
return
}
else {
Write-Host 'All is good'
}
Another alternative that achieves the same as above but makes the code simpler can be to use a ManualResetEventSlim
:
$addToMe = [System.Collections.Concurrent.ConcurrentBag[psobject]]::new()
$resetSlim = [System.Threading.ManualResetEventSlim]::new()
0..5 | ForEach-Object -ThrottleLimit 6 -Parallel {
$localAddToMe = $using:addToMe
$localResetSlim = $using:resetSlim
Write-Host $_
if ($_ -gt 3) {
$localResetSlim.Set()
return 'something, whatever here'
}
$localAddToMe.Add($_)
} | Select-Object -First 1 | Out-Null
# this type should always be disposed when no longer needed
$resetSlim.Dispose()
Write-Host $addToMe
Write-Host $numberOverLimit
if ($resetSlim.IsSet) {
Write-Host 'A number was over the limit'
return
}
else {
Write-Host 'All is good'
}