Почему add_EventName не работает с таймером?

В PowerShell вы можете подписаться на событие, используя add_NameOfEvent({scriptblock})-метод объекта. Это хорошо работает для объектов формы, таких как кнопки и т. д. Однако, когда я попробовал это с 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 отличается от бывшего. компонент формы (где работает метод 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's added
PS > $b.PerformClick()
button

person Frode F.    schedule 19.03.2013    source источник


Ответы (2)


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

$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, но я думаю, что это возможно с помощью некоторого кода C#/Pinvoke.

person CB.    schedule 26.03.2013
comment
+1 за хороший ответ. Я читал о synchronizingobject до того, как спросил, и я думаю, что обычный метод добавления элемента управления, который вы можете использовать для добавления элемента управления в форму, также устанавливает свойство syncronizingobject. Однако должна быть переменная/объект, который я могу использовать для синхронизации таймера с консолью. Я считаю это правильным ответом, но подожду пару дней, чтобы отметить его, если кто-то сможет предоставить полное решение (без создания фиктивной формы) :-) - person Frode F.; 26.03.2013
comment
Graimer Я надеюсь, что @x0n сможет дать лучший ответ, у него есть глубокие знания о событиях PowerShell. В любом случае, я нашел ваш вопрос действительно интересным! - person CB.; 27.03.2013

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

person Christopher Douglas    schedule 19.03.2013
comment
Я знаю, что вам нужно подписаться на событие, но для этого используется ярлык add_eventname. Чтобы блок сценария непосредственно обрабатывал событие .NET, вызовите источник метода Add_Event() этого объекта: pavleck.net/powershell-cookbook/ch31.html . Как уже говорилось, этот ярлык работает с компонентами формы .net, так чем же они отличаются от таймера .net? :С - person Frode F.; 19.03.2013
comment
Этот метод также может быть специфичен для форм, я не уверен, почему он отображается в powershell с помощью командлета Get-Member -Force. Однако, поскольку add_event() не отображается в powershell без использования параметра force для get-member, у меня есть ощущение, что это может быть сделано намеренно. Я также заметил, что в источнике, на который вы ссылаетесь, он упоминает использование Add_Event(), но на самом деле никогда его не использует. Вместо этого автор регистрирует событие. В этой документации указано, что класс Timer не имеет метода add_event(). msdn.microsoft.com/en-us/library/ - person Christopher Douglas; 19.03.2013
comment
add_eventname — это скрытый/динамический метод, который обрабатывает PS. Его не видно ни на одном объекте. Если вы наберете $timer1.add_Elapsed, вы получите его перегрузки в качестве доказательства того, что он существует. - person Frode F.; 19.03.2013
comment
Я вижу перегрузки метода Add_elapsed, но он требует делегата .net с именем ElapsedEventHandler, без конструктора я не могу создать с ним новый объект в powershell для передачи в метод add_elapsed. - person Christopher Douglas; 19.03.2013
comment
Он принимает значение обработчика событий. Я не собираюсь писать здесь книгу, чтобы объяснить, как это работает. Важно то, что проблема не в методе. Это что-то другое. бывший. что таймер работает в отдельном сеансе/потоке/независимо от того, что он не связан с моей консолью powershell и из-за этого не может писать на него. Думаю, мне нужна помощь супергероев. Мне нужны Кит Хилл и Шей Леви! :П - person Frode F.; 19.03.2013
comment
Я должен задаться вопросом, не является ли этот конкретный метод одним из тех, которые предназначены только для внутреннего использования, которые простые смертные не могут использовать. Я не уверен, почему вы хотели бы в любом случае. Кажется, что если бы вы действительно могли включить это, чтобы вы генерировали событие каждый раз, когда ET изменяется на этом таймере, единственное, что вы могли бы сделать, это съесть ядро, генерирующее события быстрее, чем вы могли бы их обработать. . - person mjolinor; 22.03.2013