How to exit from ForEach-Object Parallel and update a variable outside of Runspace in Powershell 7?

428 Views Asked by At

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
1

There are 1 best solutions below

2
Santiago Squarzon On BEST ANSWER

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 pipeline break, such as a ForEach-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.

$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'
}