How To Gracefully Stop An Asynchronous Job In PowerShell

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?

Jorge Candeias's Picture

About Jorge Candeias

Jorge helps organizations build high-performing solutions on the Microsoft tech stack.

London, United Kingdom https://jorgecandeias.github.io