Home > Back-end >  Timers.Timer paralyze script
Timers.Timer paralyze script

Time:09-27

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.
  • 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 time Unregister-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
}
  • Related