Keep PowerShell process alive without interrupting asynchronous event handlers

Viewed 636

Summary

I'd like to know the best way to keep a PowerShell script running, without putting the main thread to sleep. Using Start-Sleep to keep a script alive has the side effect of blocking asycnhronous event handlers that are running on the same thread.

Example Code

Consider the following script, which demonstrates this blocking in action.

$Timer = [System.Timers.Timer]::new(500)
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action { Write-Host -ForegroundColor Blue hi }
$Timer.Start()
Start-Sleep -Seconds 5

When running this script, you'll see that the Timer is blocked from executing its handlers until the Start-Sleep command has completed. However, you'll also notice that the events are queued up in the background, and all fire in quick succession once the main thread is released.

Also, the script exits once the Start-Sleep command has finished running.

enter image description here

Question: How do I keep the PowerShell process / script running, without blocking event handlers?

1 Answers

I asked a similar question on the PowerShell Polaris repository on GitHub about a year ago. The answer for that specific module is to use the PowerShell Wait-Event command.

Using the Wait-Event command, with a random -SourceIdentifier will block further execution of the main thread, without blocking event handlers from executing.

$Timer = [System.Timers.Timer]::new(500)
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action { Write-Host -ForegroundColor Blue hi }
$Timer.Start()
Wait-Event -SourceIdentifier SomeEventIdThatWillNeverExist

Conditional Termination

If the script has "completed" (whatever that means to the author), and the registered event handlers wish to terminate the script, they can do so.

The -Action event handler on the Timer, in this example, could optionally fire an event with the SomeEventIdThatWillNeverExist as the value for its -SourceIdentifier parameter, using the built-in New-Event command. Otherwise, an outside could forcefully terminate the process when appropriate.

$Timer = [System.Timers.Timer]::new(500)
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action {
  Write-Host -ForegroundColor Blue hi
  if ((Get-Random -Maximum 100) -gt 95) {
    New-Event -SourceIdentifier SomeEventIdThatWillNeverExist
  }
}
$Timer.Start()
Wait-Event -SourceIdentifier SomeEventIdThatWillNeverExist

In an interactive PowerShell session, the Timer will continue to run after the script has completed execution. You would need to invoke the script using a separate PowerShell process (ie. pwsh -File timer.ps1).

enter image description here

At the moment, this is the best answer that I am aware of. However, if there's a better way of accomplishing this, I'm open to new answers.

Related