How To Gracefully Stop An Asynchronous Job In PowerShell
This article explains how to stop an asynchronous job in PowerShell.
Question
How can we ask an asynchronous PowerShell job to gracefully stop on its own?
Short Answer
We can use a marker file pattern for that effect:
# this is the path to the marker file
$JobStopMarkerFile = "C:TempJobStopMarker.txt";
# ensure the marker file does not exist
if (Test-Path -LiteralPath $JobStopMarkerFile)
{
Remove-Item $JobStopMarkerFile
}
# this contains the script to run async
$JobScript = {
param
(
[Parameter(Mandatory = $true)]
$JobStopMarkerFile
)
# this flag will be set when the job is done
$IsJobDone = $false
# this condition checks whether
# 1) the job is done
# 2) the job has been asked to stop
while (-not $IsJobDone)
{
# some recurring task would run here instead
Start-Sleep -Seconds 5
# uncomment and set this whenever the job finishes on its own
# $IsJobDone = $true
# check if the marker file exists
if (Test-Path -LiteralPath $JobStopMarkerFile)
{
# signal the job as completed
$IsJobDone = $true;
# cleanup the file too
Remove-Item -LiteralPath $JobStopMarkerFile
}
}
}
# start the job now
$Job = Start-Job -ScriptBlock $JobScript -ArgumentList $JobStopMarkerFile
# do some other stuff here
Start-Sleep 1
# ask the job to stop
# we do this by *creating* an empty marker file
# the job itself will remove this file
$null > $JobStopMarkerFile
# wait for the job to finish
Wait-Job $Job
There is quite some code here, isn’t there? So how does it work?
Long Answer
If we just want to stop a job, minus the gracefully bit, we can simply use the Stop-job
Cmdlet:
Stop-Job $Job
Easy enough, but with this Cmdlet we have no control over where exactly the job stops. If we need that level of control we’ll have to use some sort of flag pattern. One way to achieve this is to use what’s called a marker file. This is our red stop flag.
We declare the location of this file with this code:
$JobStopMarkerFile = "C:TempJobStopMarker.txt";
To avoid repeating code, the job takes this location as a parameter:
param
(
[Parameter(Mandatory = $true)]
$JobStopMarkerFile
)
Now we start working some magic. Here, our recurring task checks whether it should continue or not. This decision depends on the $IsJobDone
variable:
while (-not $IsJobDone)
{
(...)
}
If the job finishes whatever work it is doing on its own, we can set this variable to stop the loop nicely:
$IsJobDone = $true
However, we also keep checking if someone out there has created that marker file for us:
if (Test-Path -LiteralPath $JobStopMarkerFile)
{
(...)
}
If this has happened, it means someone wishes the job to stop. This means we have to do two things.
First, we have to signal the job as completed, so the loop ends nicely:
$IsJobDone = $true
Then, we should remove the marker file as well.
Remove-Item -LiteralPath $JobStopMarkerFile
This is a bit of defensive code, just in case the calling code forgets to do so. If the file is left there accidentally, the job will detect it the next time around and finish during the very first loop.
Having this in place, the calling code now only needs to create that marker file in order to stop the job. An empty file will do just fine:
$null > $JobStopMarkerFile
Optionally, the calling code can also wait for the job to finish gracefully before proceeding with any other tasks:
Wait-Job $Job
Easy, isn’t it?