PowerShell Genie

PowerShell Thread jobs

Start-ThreadJob does what Start-Job does, but in a thread inside the current process instead of a whole new process. It ships with PowerShell 7 and is available for 5.1 from the gallery as the ThreadJob module.

Lesson 2 min read Windows PowerShell 5.1 and PowerShell 7

Documentation: Microsoft Learn

Overview

The difference is cost. A background job spawns a process, which takes a fifth of a second and tens of megabytes. A thread job costs a fraction of that. For fifty short tasks that gap decides whether the script finishes in seconds or minutes.

Everything else is the same: Get-Job, Receive-Job, Wait-Job and Remove-Job all work on thread jobs, so you can swap Start-Job for Start-ThreadJob and keep the rest of the script.

The trade-off is isolation. Threads share the process, so one thread crashing the runtime takes the others with it, and shared state needs care. Use -ThrottleLimit to cap how many run at once, because launching two hundred threads against one machine will hurt rather than help.

Rule of thumb: thread jobs for many short tasks, background jobs when you need one task genuinely isolated.

Inspect this example’s structure

Read the command and its supplied arguments. This example requires real PowerShell or its module; this diagram does not execute it.

$targets = 'WEB01' , 'WEB02' , 'SQL01' , 'DC01' $targets | ForEach-Object { [pscustomobject]@{ Target = $_ Job = 'Start-ThreadJob' Cost = 'thread, not process' } }
  1. Stage 1$targetsPositional values or no named parameters
  2. Stage 2ForEach-ObjectPositional values or no named parameters

    An example to inspect

    Preview the example. Sign in for the full lesson and activities.

    $targets = 'WEB01' , 'WEB02' , 'SQL01' , 'DC01'
    $targets | ForEach-Object {
    [pscustomobject]@{
        Target = $_
        Job = 'Start-ThreadJob'
        Cost = 'thread, not process'
      } }

    What happens

    This builds one descriptor per target rather than starting real jobs, since jobs would outlive the command. In practice the body would be $targets | ForEach-Object { Start-ThreadJob -ScriptBlock { param($t) Test-Connection $t } -ArgumentList $_ } followed by Get-Job | Wait-Job | Receive-Job.

    Worth remembering

    • Threads instead of processes, so startup is far cheaper.
    • Built into PowerShell 7; install the ThreadJob module for 5.1.
    • The same Get-Job, Receive-Job and Wait-Job cmdlets apply.
    • Cap concurrency with -ThrottleLimit.

    Primary documentation

    ForEach-Object reference

    Try it

    Illustrative output · values vary by environment

    1PS> $targets = 'WEB01' , 'WEB02' , 'SQL01' , 'DC01'
    2PS>
    3PS> $targets | ForEach-Object {
    4PS> [pscustomobject]@{
    5PS> Target = $_
    6PS> Job = 'Start-ThreadJob'
    7PS> Cost = 'thread, not process'
    8PS> } }

    Example reader

    Inspect this page's example

    Fictional browser data when supported

    Run supported commands against the practice data, or inspect the example without executing it.

    Ready to inspect.

      Builds one job descriptor per target to show the pattern.

      Show reference output

      Illustrative output · values vary by environment

      Target  Job              Cost
      ------  ---              ----
      WEB01   Start-ThreadJob  thread, not process
      WEB02   Start-ThreadJob  thread, not process
      SQL01   Start-ThreadJob  thread, not process
      DC01    Start-ThreadJob  thread, not process

      Keep going

      Next: PowerShell ForEach-Object -Parallel
      Genie guidePowerShell Thread jobs

      Start-ThreadJob does what Start-Job does, but in a thread inside the current process instead of a whole new process. It ships with PowerShell 7 and is available for 5.1 from the gallery as the ThreadJob module.