I'm trying to run this example:
Clear-Host
$timer = [Timers.Timer]::new()
$timer.Interval = 200
$action = {
if($complete -eq 8){
write-host "completed $complete" -ForegroundColor Red
write-host "Timer stopped" -ForegroundColor Red
$timer.stop()
Unregister-Event thetimer
}
}
Register-ObjectEvent -InputObject $timer -EventName elapsed `
–SourceIdentifier thetimer -Action $action
$complete = 0
$timer.Start()
while($true){
Write-Host "Script working: $complete" -ForegroundColor Green
Start-Sleep -m 400
$complete
}
But when timer stops, the script is paralyzed.
What i'm doing wrong, and how to fix it?
CodePudding user response:
Your code fundamentally won't work as intended unless you execute it directly in the global scope (by pasting it at the prompt or by dot-sourcing a script that contains it).
- The reason is that a script block passed to
Register-ObjectEvent
's-Action
parameter runs inside a dynamic module, which sees only the global scope's variables, not that of any other caller, such as a script's.
- The reason is that a script block passed to
If you do run from the global scope, the hang is caused by calling
Unregister-Event
inside the-Action
script block:Call it from the caller's scope instead (i.e. from the same scope where
Register-ObjectEvent
was called).That the code hangs when called from inside the
-Action
script block (happens up to at least PowerShell 7.1, the version current as of this writing) should be considered a bug; it seems to occur when more events are pending at the timeUnregister-Event
is called; the problem has been reported in GitHub issue #16154
The following is a self-contained, working sample that you can also invoke from a script:
$timer = [Timers.Timer]::new()
$timer.Interval = 200
$action = {
# Note: This script block generally does NOT see the caller's variables.
$timer = $Event.Sender # originating timer object
$counter = $Event.MessageData.Value # the variable object passed to -MessageData
# Note: Since you're only *getting* and *integer* value, you needn't worry
# about thread synchronization.
if ($timer.Enabled -and $counter -ge 8) {
write-host "completed $counter" -ForegroundColor Red
write-host "Timer stopped" -ForegroundColor Red
# Refer to the originating timer object with $Event.Sender or $args[0]
$timer.stop()
# Do NOT try to call Unregister-Event here - it'll cause a hang.
}
}
# Initialize the counter variable.
$complete = 0
# Get the variable as a variable *object* that can be passed to the -Action
# script block with -MessageData.
$counterVarObject = Get-Variable complete
# Register the event and pass the variable object with -MessageData.
$null = Register-ObjectEvent -InputObject $timer -EventName elapsed `
–SourceIdentifier thetimer `
-Action $action -MessageData $counterVarObject
$timer.Start()
try {
while ($timer.Enabled) { # Loop until the timer stops.
Write-Host "Script working: $complete" -ForegroundColor Green
Start-Sleep -m 400
$complete
}
}
finally { # This is called even if the loop is aborted with Ctrl-C.
# Unregister the event from the *caller's scope.
Unregister-Event theTimer
}