I tried to utilize thread-safe DotNET classes (like BlockingCollection
, ConcurrentQueue
) and static functions (like Interlocked::Increment()
) in my multithreaded PowerShell script (achieved through RunSpacePool
), but those seem to behave in unexpected ways in PowerShell.
BlockingCollection.Take()
function occasionally serves the same unique value to two separate threads, and Interlocked::Increment()
function seems to be non-atomic because it increments a shared variable to the same value on different threads.
Consider the following simplified script as an example (you can copy-paste it to your favorite scripting environment and do a "run-selected" to observe your result, be careful to run the first part alone first):
[uint64]$x = 0
$ss = [Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$tp = [runspacefactory]::CreateRunspacePool(1, 10, $ss, $Host)
$tp.ApartmentState = "STA";
$tp.Open()
for ($idx = 0; $idx -lt 10; $idx++) {
$ps = [powershell]::Create();
$ps.RunspacePool = $tp
$null = $ps.AddScript({
Param ([Ref]$xRef)
for ($idx = 0; $idx -lt 10000; $idx++) {
[System.Threading.Interlocked]::Increment($xRef) } })
$null = $ps.AddParameter("xRef", [Ref]$x)
$null = $ps.BeginInvoke() }
# Select & run this part below separately, giving enough time for threads to
# finish gracefully - you can look at the value of $x to make sure it doesn't
# get updated anymore.
$tp.Close()
$tp.Dispose()
$x
In this script, 10 threads are created which increment a shared variable $x
10.000 times each through Interlocked::Increment()
, so I would expect the variable to have a value of 100.000 at the end, if Interlocked behaved as advertised (atomic). But result never comes to even close of it, mostly I get results between 50.000 - 70.000.
My environment is PowerShell 5.1 (5.1.16299.547) on Windows 10 1803 x64 (10.0.16299.547), CLRVersion being 4.0.30319.42000.
What could be the reason for this, a fault in my code/logic, boxing-unboxing or whatever overhead of PowerShell accessing DotNet layer, or else?
What is your result running the above code? Could this situation be avoided somehow or are DotNet thread-safe classes & functions useless for PowerShell multithreading?