multithreadingpowershellscopepowershell-corepowershell-jobs

Modifying PowerShell $using scope variables in thread started with Start-ThreadJob


If I understand the PowerShell Scopes documentation, it should be possible to assign to $using scope variables from threads started using Start-ThreadJob. The documentation says (emphasis mine):

The Using scope modifier is supported in the following contexts:

  • ...
  • Thread jobs, started via Start-ThreadJob or ForEach-Object -Parallel (separate thread session)

Depending on the context, embedded variable values are either independent copies of the data in the caller's scope or references to it.
...
In thread sessions, they are passed by reference. This means it is possible to modify call scope variables in a different thread. To safely modify variables requires thread synchronization.

Yet the following fails to run:

$foo = 1

Start-ThreadJob {
    Write-Host $using:foo
    $using:foo = 2
} | Wait-Job | Out-Null

Write-Host $foo

It errors on $using:foo = 2 with:

The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept assignments, such as a variable or a property.

Printing the variable with Write-Host $using:foo works correctly.

I'm using PowerShell 7.1.


Solution

  • You can't overwrite the $using: variable reference - but you can use it to dereference a variable value in the calling scope, at which point you can then mutate it (assuming a reference-type value was assigned to the original variable):

    $foo = @{ 
      Value = 1
    }
    Start-ThreadJob {
        Write-Host $using:foo
        $foo = $using:foo
        $foo.Value = 2
    } | Wait-Job | Out-Null
    
    Write-Host $foo.Value
    

    To ensure thread synchronization, I'd recommended a synchronized hashtable as your container type:

    $foo = [hashtable]::Synchronized(@{ 
      Value = 1
    })
    
    1..4 |%{Start-ThreadJob {
        Write-Host $using:foo
        $foo = $using:foo
        $foo.Value++
    }} | Wait-Job | Out-Null
    
    Write-Host $foo.Value
    

    At which point you should see the (4-times incremented) value of 5