Почему функция add_EventName не работает с таймером?

В PowerShell вы можете подписаться на событие, используя add_NameOfEvent({scriptblock}) -метод объекта. Это хорошо работает для объектов Form, таких как кнопки и т.д. Однако, когда я пробовал его с помощью System.Timers.Timer, он не работал. Почему это? Пример:

$timer1 = New-Object System.Timers.Timer
$timer1.Interval = 2000
$timer1.add_Elapsed({ Write-Host "Timer1 tick" })

$timer2 = New-Object System.Timers.Timer
$timer2.Interval = 2000
Register-ObjectEvent -InputObject $timer2 -EventName Elapsed -Action { Write-Host "Timer2 tick" }

$timer1.Start()
$timer2.Start()

$timer2 будет работать нормально, но $timer1 никогда не будет писать на консоль. Что отличает Timer от ex. компонент формы (где работает метод add_...)? Выполняется ли Timer в отдельном потоке и из-за этого записывается в "скрытую" консоль?

Доказательство того, что метод работает с компонентами формы для тех, кто не знаком с ним:

PS > Add-Type -AssemblyName System.Windows.Forms
PS > $b = New-Object System.Windows.Forms.Button
PS > $b.add_click({ Write-Host "button" })
#Get-EventSubscriber won't show this event, but it added
PS > $b.PerformClick()
button

Ответ 1

если вы попробуете этот код:

$w = new-object 'System.Windows.Forms.Form'
$timer1 = New-Object System.Timers.Timer
$timer1.Interval = 2000
$timer1.add_Elapsed({ Write-Host "Timer1 tick" })
$timer1.SynchronizingObject = $w
$timer1.Start()
$w.ShowDialog()

вы увидите ожидаемое поведение, потому что System.Timers.Timer может синхронизироваться с формой окна (как в коде С#).

Проблема в консоли powershell заключается в том, что таймер создается в другом потоке и не может синхронизироваться с консолью, потому что он не предоставляет интерфейс ISynchronizeInvoke, представляющий объект, используемый для маршалирования вызовов обработчика событий, которые когда истекает интервал.

Когда SynchronizingObject - null, метод, обрабатывающий событие Elapsed, вызывается в потоке из пула системных потоков.

IMO За сценой Register-ObjectEvent создает пул системных потоков для выполнения вызовов обработчика событий.

Я не могу воссоздать вручную, что Register-ObjectEvent делать, но я думаю, что это возможно с некоторым кодом С#/Pinvoke.

Ответ 2

Эта статья указывает мне, что вы должны подписаться на события, чтобы они могли быть подняты в движке PowerShell. Похоже, что у вас есть концепция для $timer2. Я пытаюсь запустить $timer1 в своей собственной сессии и, похоже, не работает с использованием методов, открытых, я специально ссылаюсь на .Start().