PowerShell / Advanced: remote boundaries, security, desired state, and reliability / Lesson 16
Bound parallel work and collect real completion
Separate background processes, thread jobs, runspaces, parallel pipelines, shared state, output ordering, and final job outcomes.
On this page
Lesson goals
- Explain which parallel features require PowerShell 7 or extra modules.
- Preserve task identity, bounds, and completion evidence.
- Avoid unsafe shared mutation and assumed result order.
Parallel mechanisms have different isolation boundaries
An out-of-process background job, an in-process thread job, remoting, and ForEach-Object -Parallel do not have identical runtime, serialization, memory, or failure behavior. Thread-job support is a module dependency to declare where needed. -Parallel belongs to PowerShell 7-era functionality and is not available in Windows PowerShell 5.1. A job name or a pipe character does not reveal its process boundary. Choose the mechanism from task independence, data size, cancellation, dependency requirements, and effect ownership. Do not enable parallelism merely because sequential code looks verbose or because a small synthetic example can launch several workers.
A concurrency limit is not a total resource budget
Throttle limits can bound concurrent work at the documented mechanism’s scope, but each worker can still launch children, hold memory, or overload a shared dependency. Set an explicit finite input and queue policy as well as concurrency. A larger worker count can worsen latency or failure rate when the downstream stage is already constrained. Preserve the identity of each input so errors and partial results can be reconciled. A limit of two in a teaching example is a declared illustration, not a general tuning recommendation for any computer or service. No load test or host job is executed here.
Keep parallel work synthetic and results identifiable
The illustration maps four declared numbers to result records, then sorts by input for deterministic presentation. Actual parallel completion order is not guaranteed by the source order. Sorting after collection has its own memory and latency cost, which should be documented for large workloads. The block remains non-runnable because no PowerShell engine is present, and it launches no workers in this course. A real effectful adapter must preserve per-item authorization and idempotence; parallelizing an unsafe operation does not repair its semantics or make cancellation transactional.
1..4 | ForEach-Object -Parallel {
[pscustomobject]@{ Input = $_; Result = ($_ * 2) }
} -ThrottleLimit 2 | Sort-Object Input
The intended records map 1–4 to 2,4,6,8. PowerShell parallel scheduling, runspace behavior, and ordering were not executed or validated.
Started and completed are different outcomes
A job launch acknowledgement does not prove the operation completed or that every child record succeeded. Own job identifiers, wait/receive behavior, errors, timeouts, cancellation, and cleanup. A job state or received output must be interpreted with its mechanism’s documented error channels and child-job structure. Preserve partial results only under an explicit policy. The offline model below checks a finite task-result table and stable presentation; it does not start jobs or prove concurrency correctness. Later authorized tests should include early failure, interrupted waiting, multiple failures, and resource cleanup without touching production state.
completed_out_of_order = [{'Input': 3, 'Result': 6}, {'Input': 1, 'Result': 2}, {'Input': 4, 'Result': 8}, {'Input': 2, 'Result': 4}]
ordered = sorted(completed_out_of_order, key=lambda row: row['Input'])
assert len({row['Input'] for row in ordered}) == 4
print(','.join(str(row['Result']) for row in ordered))
2,4,6,8
The fixture is a declared completion list, not actual workers or timing evidence. It verifies one ordered result contract only.
Common errors and fixes
Parallel results are assumed source-ordered
- Why it happens
- Input order was confused with completion order.
- How to fix it
- Preserve identities and order explicitly if required.
- Prevent it next time
- Test intentionally reordered completions.
A shared list is mutated without coordination
- Why it happens
- Scope access was mistaken for thread safety.
- How to fix it
- Use independent results or supported synchronization.
- Prevent it next time
- Review memory sharing per job mechanism.
Job launch is reported as business completion
- Why it happens
- Final status and result collection were omitted.
- How to fix it
- Own and collect all required job outcomes.
- Prevent it next time
- Test timeout and partial-failure paths.
Check your understanding
1. Why can raising a throttle limit reduce useful throughput?
Show worked solution
Workers can compete for memory or saturate a shared downstream dependency, increasing queues, retries, or errors. Measure equivalent successful work and latency before changing concurrency, and retain a bounded recovery policy.
2. What should a parent aggregate retain for every worker?
Show worked solution
Retain input identity, final execution state, relevant output, failure details, and cleanup ownership. A count of printed records alone cannot prove all required operations completed exactly once or that no hidden job remains.
Key takeaways
- Choose a declared isolation model and bound work.
- Preserve identity and collect real completion before acceptance.