Skip to content
AgileLogics
6 min read

Parallelism and Jobs

Prerequisites: Script blocks, Scope

Slow, independent tasks — pinging servers, calling APIs, processing files — finish much faster when run at the same time.

ForEach-Object -Parallel (PowerShell 7)

$timer = [System.Diagnostics.Stopwatch]::StartNew()
$results = 1..6 | ForEach-Object -Parallel {
  Start-Sleep -Milliseconds 500
  "task $_ done"
} -ThrottleLimit 6
$timer.Stop()

$results | Sort-Object
$timer.Elapsed.TotalSeconds -lt 2
task 1 done
task 2 done
task 3 done
task 4 done
task 5 done
task 6 done
True

Six half-second tasks finished in well under the 3 seconds they'd take one after another.

  • Each item runs in its own runspace (a separate PowerShell engine in the same process).
  • -ThrottleLimit caps how many run at once (default 5).
  • Results arrive in completion order, not input order — sort if order matters.

$using: for outside variables

Parallel script blocks can't see your variables; prefix them with $using::

$prefix = 'server'
1..3 | ForEach-Object -Parallel { "$using:prefix-$_" } | Sort-Object
server-1
server-2
server-3

Don't modify shared collections from parallel blocks — output results and collect them instead. If you must share, use a thread-safe type like [System.Collections.Concurrent.ConcurrentBag[object]].

Thread jobs and background jobs

Jobs run work in the background while your script (or console) carries on:

$job = Start-ThreadJob -ScriptBlock { param($n) 1..$n | Measure-Object -Sum | Select-Object -ExpandProperty Sum } -ArgumentList 100
'Doing other work...'
$job | Wait-Job | Receive-Job
$job.State
$job | Remove-Job
Doing other work...
5050
Completed
Command Runs in Start-up cost
ForEach-Object -Parallel runspaces low
Start-ThreadJob runspaces low
Start-Job a separate process high — but isolated
Invoke-Command -AsJob remote computers —

In PowerShell 7, you can also append & to a pipeline to run it as a job: Get-ChildItem -Recurse C:\ &.

When not to parallelize

Short tasks are often slower in parallel, because starting runspaces costs time. Parallelize waits (network, disk, sleep), not tiny calculations — and measure with Measure-Command.