PowerShell:未执行表单的作业事件操作 [英] PowerShell: Job Event Action with Form not executed

查看:19
本文介绍了PowerShell:未执行表单的作业事件操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果我运行以下代码,则执行事件操作:

If I run the following code, the Event Action is executed:

$Job = Start-Job {'abc'}
Register-ObjectEvent -InputObject $Job -EventName StateChanged `
    -Action {
             Start-Sleep -Seconds 1
             Write-Host '*Event-Action*'
            } 

显示字符串Event-Action".

如果我使用表单并通过单击按钮启动上述代码,

If I use a Form and start the above code by clicking a button,

未执行事件操作:

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$Form1 = New-Object Windows.Forms.Form
$Form1.Add_Shown({
   $Form1.Activate()
})
$Button1 = New-Object System.Windows.Forms.Button
$Button1.Text = 'Test'
$Form1.Controls.Add($Button1)
$Button1.Add_Click({
   Write-Host 'Test-Button was clicked'
   $Job = Start-Job {'abc'}
   Register-ObjectEvent -InputObject $Job -EventName StateChanged `
       -Action {
                Start-Sleep -Seconds 1
                Write-Host '*Event-Action*'
               }
})
$Form1.ShowDialog()

只有当我再次单击按钮时,才会执行第一个事件操作.

Only when I click the button again, the first Event Action is executed.

第三次点击,第二个事件动作被执行,依此类推.

With the third click the second Event Action is executed and so on.

如果我快速连续多次点击,结果是不可预测的.

If I do multiple clicks in rapid succession, the result is unpredictable.

此外,当我使用右上角的按钮关闭表单时,

Furthermore when I close the form with the button in the upper right corner,

执行最后一个打开"事件操作.

the last "open" Event Action is executed.

注意:为了测试 PowerShell ISE 是首选,因为 PS Console 显示

Note: For testing PowerShell ISE is to be preferred, because PS Console displays

字符串仅在某些情况下.

the string only under certain circumstances.

有人可以告诉我这里发生了什么吗?

Can someone please give me a clue what's going on here?

提前致谢!

尼米森.

感谢您的解释,但我不太明白,为什么在对 Form 执行某些操作之前,StateChanged 事件不会被触发或对主脚本可见.如果我再次尝试向我解释它,我将不胜感激.

Thanks for your explanation, but I don't really understand, why the StateChanged event is not fired or visible to the main script until there is some action with the Form. I'd appreciate another attempt to explain it to me.

我想要完成的是一种使用 PowerShell 和 Forms 的多线程.

What I want to accomplish is a kind of multithreading with PowerShell and Forms.

我的计划如下:

'

脚本向用户显示一个表单.

The script shows a Form to the user.

用户进行一些输入并点击按钮.根据用户的输入,使用 Start-Job 启动一组作业,并为每个作业注册一个 StateChanged 事件.

The user does some input and clicks a button. Based on the user's input a set of Jobs are started with Start-Job and a StateChanged event is registered for each job.

当作业运行时,用户可以在表单上执行任何操作(包括通过按钮停止作业),并在必要时重新绘制表单.

While the Jobs are running, the user can perform any action on the Form (including stop the Jobs via a button) and the Form is repainted when necessary.

脚本会对由表单或其子控件触发的任何事件做出反应.

The script reacts to any events which are fired by the Form or its child controls.

脚本也会对每个作业的 StateChanged 事件做出反应.

Also the script reacts to each job's StateChanged event.

当 StateChanged 事件发生时,检查每个作业的状态,如果所有作业的状态都为已完成",则使用 Receive-Job 获取作业的结果并显示给用户.

When a StateChanged event occurs, the state of each job is inspected, and if all jobs have the state 'Completed', the jobs' results are fetched with Receive-Job and displayed to the user.

'

除了 StateChanged 事件对主脚本不可见之外,所有这些都正常工作.

All this works fine except that the StateChanged event is not visible to the main script.

以上仍然是我最喜欢的解决方案,如果您有任何想法如何实施,请告诉我.

The above is still my favorite solution and if you have any idea how to implement this, please let me know.

否则我很可能会求助于一种解决方法,这至少会给用户一种多线程的感觉.如下例所示:

Otherwise I'll most likely resort to a workaround, which at least gives the user a multithreading feeling. It is illustrated in the following example:

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$Form1 = New-Object Windows.Forms.Form
$Form1.Add_Shown({
   $Form1.Activate()
})
$Button1 = New-Object System.Windows.Forms.Button
$Button1.Text = 'Test'
$Form1.Controls.Add($Button1)
$Button1.Add_Click({
   $Form1.Focus()
   Write-Host 'Test-Button was clicked'
   $Job = Start-Job {Start-Sleep -Seconds 1; 'abc'}
   Do {
      Start-Sleep -Milliseconds 100
      Write-Host 'JobState: ' $Job.State
      [System.Windows.Forms.Application]::DoEvents()
   }
   Until ($Job.State -eq 'Completed')
   Write-Host '*Action*'
})
$Form1.ShowDialog()

推荐答案

有很多 (StackOverflow) 的问题和答案'持久神秘' 将表单(或 WPF)事件与 .NET 事件(如 EngineEventsObjectEventsWmiEvents)结合起来) 在 PowerShell 中:

There are a lot of (StackOverflow) questions and answers about this ‘enduring mystique’ of combining form (or WPF) events with .NET events (like EngineEvents, ObjectEvents and WmiEvents) in PowerShell:

它们都归结为两点:即使设置了多个线程,一个线程中也有两个不同的侦听器".当您的脚本准备好接收表单事件(使用 ShowDialogDoEvents)时,它无法同时侦听 .NET 事件.反之亦然:如果脚本在处理命令(如 Start-Sleep 或使用 Wait-Event 之类的命令专门监听 .NET 事件时打开>Wait-Job),你的表单将无法监听表单事件.这意味着 .NET 事件或表单事件正在排队仅仅是因为您的表单与您尝试创建的 .NET 侦听器位于同一线程中.与 nimizen 示例一样,在第一个龟头处看起来是正确的,当您检查后台工作人员的状态时,您的表单将对所有其他表单事件(按钮单击)无响应,并且您必须一遍又一遍地单击按钮再看看是否还是'*Doing Stuff'.要解决此问题,您可以考虑在循环中组合 DoEvents 方法,同时不断检查后台工作人员的状态,但这看起来也不是一个好方法,请参阅:Application.DoEvents() 的使用所以唯一的出路(我明白)是让一个线程在另一个线程中触发表单,我认为这只能通过使用 [runspacefactory]::CreateRunspace() 因为它能够在处理之间同步表单控件并直接触发表单事件(例如 TextChanged).

They are all come down two one point: even there are multiple threads setup, there are two different 'listeners' in one thread. When your script is ready to receive form events (using ShowDialog or DoEvents) it can’t listen to .NET events at the same time. And visa versa: if script is open for .NET events while processing commands (like Start-Sleep or specifically listen for .NET events using commands like Wait-Event or Wait-Job), your form will not be able to listen to form events. Meaning that either the .NET events or the form events are being queued simply because your form is in the same thread as the .NET listener(s) your trying to create. As with the nimizen example, with looks to be correct at the first glans, your form will be irresponsive to all other form events (button clicks) at the moment you’re checking the backgroundworker’s state and you have to click the button over and over again to find out whether it is still ‘*Doing Stuff’. To work around this, you might consider to combine the DoEvents method in a loop while you continuously checking the backgroundworker’s state but that doesn’t look to be a good way either, see: Use of Application.DoEvents() So the only way out (I see) is to have one thread to trigger the form in the other thread which I think can only be done with using [runspacefactory]::CreateRunspace() as it is able to synchronize a form control between the treats and with that directly trigger a form event (as e.g. TextChanged).

(如果有另一种方式,我渴望了解如何并查看工作示例.)

(if there in another way, I eager to learn how and see a working example.)

表单示例:

Function Start-Worker {
    $SyncHash = [hashtable]::Synchronized(@{TextBox = $TextBox})
    $Runspace = [runspacefactory]::CreateRunspace()
    $Runspace.ThreadOptions = "UseNewThread"                    # Also Consider: ReuseThread  
    $Runspace.Open()
    $Runspace.SessionStateProxy.SetVariable("SyncHash", $SyncHash)          
    $Worker = [PowerShell]::Create().AddScript({
        $ThreadID = [appdomain]::GetCurrentThreadId()
        $SyncHash.TextBox.Text = "Thread $ThreadID has started"
        for($Progress = 0; $Progress -le 100; $Progress += 10) {
            $SyncHash.TextBox.Text = "Thread $ThreadID at $Progress%"
            Start-Sleep 1                                       # Some background work
        }
        $SyncHash.TextBox.Text = "Thread $ThreadID has finnished"
    })
    $Worker.Runspace = $Runspace
    $Worker.BeginInvoke()
}

[Void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$Form = New-Object Windows.Forms.Form
$TextBox = New-Object Windows.Forms.TextBox
$TextBox.Visible = $False
$TextBox.Add_TextChanged({Write-Host $TextBox.Text})
$Form.Controls.Add($TextBox)
$Button = New-Object System.Windows.Forms.Button
$Button.Text = "Start worker"
$Button.Add_Click({Start-Worker})
$Form.Controls.Add($Button)
$Form.ShowDialog()

有关 WPF 示例,请参阅:将 PowerShell 输出(碰巧)写入 WPF UI 控件

For a WPF example, see: Write PowerShell Output (as it happens) to WPF UI Control

这篇关于PowerShell:未执行表单的作业事件操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆