Last Update: Sep 04, 2024 | Published: Apr 06, 2020
There have always been a few options for running background asynchronous tasks within PowerShell. Traditionally, PowerShell (PS) Jobs was the go to method as a job could be started and then control returned to the console. PS Jobs were always heavy, in regards to resource usage, and depending on what needed to be run, this may have outweighed the benefits of running these tasks in the background.
Starting in PowerShell 6, a new type of PowerShell Job was introduced that worked with the existing PowerShell Job cmdlets, Start-ThreadJob
. Unlike traditional PS Jobs which spawn a whole new host process for each running job, PS ThreadJobs run in multiple threads on the same process which vastly increases performance by lowering overhead.
PS Jobs are easy to start and consist of three commands that are in use.
Start-Job
Get-Job
Receive-Job
By utilizing the above three commands, we are able to start a job, see the status of a running job, and ultimately receive the results of that job. An example of this is below.
Start-Job -Name "Test One" -ScriptBlock {
Write-Host "Starting Job, sleeping for 3 seconds"
Start-Sleep -Seconds 3
Write-Host "Completing Job"
}
Get-Job
Get-Job | Receive-Job
Seems easy enough to create a regular PowerShell Job, so how about a Thread Job? Thankfully, we can use all of the same commands as before with one difference. Instead of Start-Job
, we are using Start-ThreadJob
.
Start-ThreadJob -Name "Test One" -ScriptBlock {
Write-Host "Starting Job, sleeping for 3 seconds"
Start-Sleep -Seconds 3
Write-Host "Completing Job"
}
Get-Job
Get-Job | Receive-Job
As you may have been able to tell, the PSJobTypeName
is different between the two. BackgroundJob
vs. ThreadJob
, which allows one to easily filter the Get-Job
output.
It can be difficult to compare the performance of these two jobs. Primarily because the differences are in memory usage and overall run time.
As seen with the following code block, running 5 standard PowerShell Job’s that sleep for 2 seconds each takes roughly a total of 2.8
seconds, to get started.
Measure-Command {
1..5 | ForEach-Object {
Start-Job -ScriptBlock {
Start-Sleep -Seconds 2
}
}
} | Select-Object TotalSeconds
In the process list below, you can see that the following pwsh.exe
process spawns multiple other full host processes, one for each job. Although only four are listed here, that is primarily due to difficulty in capturing all five at once.
Running the same scriptblock but using Start-ThreadJob
instead of Start-Job
shows the difference in execution time, a very small 0.02
seconds versus the 2.8
above.
Measure-Command {
1..5 | ForEach-Object {
Start-ThreadJob -ScriptBlock {
Start-Sleep -Seconds 2
}
}
} | Select-Object TotalSeconds
As is also apparent, there are no new PowerShell host processes spawned as a result of running additional Thread Job’s as these are all contained within the original PowerShell host process.
Start-Job
and Start-ThreadJob
Since Start-Job
runs a full PowerShell host process, there is more customizability that can be done. Additional abilities include, but are not limited to:
Credential
– If the sub-process needs to be run under a different user, it is possible to change the user via a PSCredential
objectAuthentication
– Similar to changing the credential, different authentication methods are available such as Basic
, CredSSP
, Digest
, Kerberos
, and Negotiate
WorkingDirectory
– As the job runs under its own process, a different working directory can be setRunAs32
– If there is a need to run the process as 32-bit rather than the default of 64-bit, this can be specified herePSVersion
– Finally, a different PowerShell version can be specified for the jobThough Start-ThreadJob
does not have those abilities, there are some additional ones that Start-ThreadJob
does have that Start-Job
does not. These include:
ThrottleLimit
– Unique to this command is the ability to spawn many threads at once, but to avoid overwhelming the system, the default limit is set at 5
but can be increasedBoth contain InitalizationScript
which can be very useful to preface the job’s being started with a script that may load extra modules or functionality.
ForEach-Object -Parallel
With PowerShell 7 came the introduction of the Parallel
parameter on the ForEach-Object
cmdlet. With the introduction of this capability, one may wonder what the background process is that is powers that ability. In this case, it is background ThreadJob
. This means that each iteration of ForEach-Object
that is passed in via the Parallel
scriptblock input, will run in it’s own thread.
Measure-Command {
1..20 | ForEach-Object -Parallel {
Write-Host "Number: $($_)"
Start-Sleep -Seconds 1
} -ThrottleLimit 20
}
For a task that needs to sleep 1 second in between each run, and typically would take 20 seconds to run without using background tasks, the whole process takes just over a second. This is due to the ThreadJobs being used in the background and running all 20 instances at once.
Though there are other methods of running parallel and asynchronous tasks within PowerShell, such as RunSpaces, a ThreadJob utilizes the existing PowerShell Jobs infrastructure and makes for an easy to use and lightweight background job capability.
Not every task will make sense to run in this way, especially those that are synchronous in nature, such that need to be run one after another, but this does add flexibility. Additionally, there is still overhead that is inherent in creating each thread so it will depend on the computational task at hand if it truly makes sense to run in a background task.
Ultimately, this offers yet another flexible option for users to further utilize PowerShell Jobs to increase the capability of their scripts and functions.