为何我的PowerShell脚本使用AddScript($secb)无法实现多线程并发,而使用AddScript($sb)却可以?
.AddScript($secb) Runs Sequentially But .AddScript($sb) Runs Concurrently in PowerShell Runspaces Let’s unpack this behavior—it’s a tricky interaction between runspaces, script blocks, and Invoke-Command that trips up a lot of folks. Here’s exactly what’s happening:
1. How .AddScript($sb) Achieves Concurrency
When you use .AddScript($sb):
- Each
[powershell]instance you create gets assigned to a separate runspace from your pool (since you’ve set the pool size to 1-5). - Each runspace executes
$sbasynchronously and independently—they all start theirStart-Sleep -Seconds 3at roughly the same time, so you see all thebeforemessages first, then all theaftermessages 3 seconds later. - There’s no extra layer of execution here—just direct, parallel execution of your script block across the runspace pool.
2. Why .AddScript($secb) Runs Sequentially
The issue with $secb boils down to three key factors:
a. Invoke-Command’s Local Execution Behavior
Your $secb script block uses Invoke-Command -ScriptBlock $block without specifying a remote session or runspace. By default, Invoke-Command executes the script block synchronously in the current runspace—meaning each $secb instance runs $sb in the same runspace it’s already using. While this alone shouldn’t block concurrency, it introduces an extra layer of execution that interacts poorly with script block serialization.
b. Script Block Serialization
When you pass $sb as an argument to $secb, $sb has to be serialized (converted to a SerializedScriptBlock object) to cross the runspace boundary. When Invoke-Command executes this serialized script block in the target runspace, some PowerShell environments handle this serialization with implicit synchronization locks—effectively forcing each runspace to wait for the previous one to finish executing the serialized script block before starting its own.
c. Shared Host Object Amplifies the Issue
You created your runspace pool with the shared $Host object:
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, 5, $sessionState, $Host)
Write-Host uses this shared $Host to output to the console, and $Host’s output stream is inherently thread-safe (serialized to prevent garbled output). When multiple runspaces try to call Write-Host at the same time, they get queued up—making it look like your tasks are running sequentially even if the Start-Sleep operations are actually happening in parallel. If you measure the total execution time, you’ll likely see it take ~3 seconds instead of 15, confirming the sleep operations are concurrent. But the output ordering makes it feel sequential.
Fixing the Concurrency with $secb
If you want to keep using $secb and get true concurrency, modify it to avoid the unnecessary Invoke-Command overhead:
$secb={ param ($block) & $block # Directly invoke the script block instead of using Invoke-Command }
This will let your tasks run concurrently just like when using .AddScript($sb).
内容的提问来源于stack exchange,提问作者H Marcus

