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

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

问题描述

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

  $ Job = Start-Job {'abc' $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ b} 

显示字符串事件动作。 p>

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



事件操作未执行:

  [System.Reflection.Assembly] :: LoadWithPartialName(System.Windows.Forms)
$ Form1 = New-Object Windows.Forms.Form
$ Form1.Add_Shown({
$ Form1.Activate()
})
$ Button1 =新对象System.Windows.Forms。按钮
$ Button1.Text ='测试'
$ Form1.Controls.Add($ Button1)
$ Button1.Add_Click({
Write-Host'Test-Button被点击'
$ Job = Start-Job {'abc'}
注册对象tEvent -InputObject $ Job -EventName StateChanged`
-Action {
Start-Sleep -Seconds 1
Write-Host'* Event-Action *'
}
} )
$ Form1.ShowDialog()

只有当我再次点击按钮时,执行事件动作。



第三次单击执行第二个事件操作,依此类推。



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



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



最后一个打开执行事件动作。



注意:为了测试PowerShell,ISE是首选,因为PS控制台只显示



情况。有人可以给我一个线索,这里发生了什么吗?



提前感谢!






nimizen。



感谢您的解释,但我不太明白,为什么StateChanged事件在主脚本中没有被触发或可见,直到有一些操作与Form。我希望再次尝试向我解释。



我想要完成的是一种PowerShell和Forms的多线程。



我的计划如下:



'



该脚本显示一个Form用户。



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



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



脚本会对任何已触发的事件做出反应通过表单或其子控件。



脚本也会响应每个作业的StateChanged事件。



StateChanged事件发生,每个作业的状态都被检查,如果所有作业的状态为已完成,则作业的结果将被Receive-Job提取并显示给用户。



'



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



以上仍然是我最喜欢的解决方案,如果您有任何想法如何实现,请让我知道。



否则我最有可能诉诸于一种解决方法,至少给用户多线程感觉。以下示例说明:

  [System.Reflection.Assembly] :: LoadWithPartialName(System.Windows.Forms )
$ Form1 = New-Object Windows.Forms.Form
$ Form1.Add_Shown({
$ Form1.Activate()
})
$ Button1 =新-Object System.Windows.Forms.Button
$ Button1.Text ='Test'
$ Form1.Controls.Add($ Button1)
$ Button1.Add_Click({
$ Form1.Focus()
Write-Host'Test-Button被点击'
$ Job = Start-Job {Start-Sleep -Seconds 1;'abc'}
Do {
Start-Sleep -Milliseconds 100
写主机'JobState:'$ Job.State
[System.Windows.Forms.Application] :: DoEvents()
}
直到($ Job.State -eq'Completed')
写入主机'* Action *'
})
$ Form1.ShowDialog()


解决方案

有很多(StackOverflow)问题和答案关于这个'持久的神秘将表单(或WPF)事件与.NET事件(例如 EngineEvents ,<$ c PowerShell中的$ c> ObjectEvents 和 WmiEvents





他们都下了两点:即使有多个线程设置,一个线程中有两个不同的侦听器。当您的脚本准备好接收表单事件(使用 ShowDialog DoEvents )时,它无法收听.NET事件与此同时。反之亦然:如果脚本在处理命令(如 Start-Sleep )时为.NET事件打开,或者使用等命令专门侦听.NET事件等待-Event Wait-Job ),您的表单将无法收听表单事件。意思是.NET事件或表单事件正在排队,因为您的表单与您尝试创建的.NET侦听器在同一个线程中。
与nimizen示例一样,在第一个龟头看起来是正确的,您的表单将不负责所有其他表单事件(按钮点击),当您检查后台工作者的状态,您必须单击按钮一遍又一遍地找出它是否仍然'*做东西'。要解决这个问题,您可以考虑在循环中组合 DoEvents 方法,同时不断检查后台工作人员的状态,但这看起来不是一个好办法:使用Application.DoEvents()
所以唯一的出路(我看到)是要有一个线程来触发另一个线程中的表单,我认为只能使用 [runspacefactory] ​​:: CreateRunspace() TextChanged )。



(如果有另一种方式,我渴望学习如何查看工作示例。)



表单

 函数启动工作器{
$ SyncHash = [hashtable] :: Synchronized(@ {TextBox = $ TextBox})
$ Runspace = [runspacefactory] ​​:: CreateRunspace()
$ Runspace.ThreadOptions =UseNewThread#另请考虑: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%
启动睡眠1#某些后台工作
}
$ SyncHash .TextBox.Text =Thread $ ThreadID已经完成
})
$ Worker.Runspace = $ Runspace
$ Worker.BeginInvoke()
}

[Void] [System.Reflection.Assembly] :: LoadWithPartialName(System.Windows.Forms)
$ Form =新对象Windows.Forms.Form
$ TextBox =新对象窗口.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 示例,请参阅:


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*'
            } 

The string 'Event-Action' is displayed.

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

the Event Action is not executed:

[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.

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?

Thanks in advance!


nimizen.

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.

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

My plan is the following:

'

The script shows a Form to the user.

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.

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

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.

'

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()

解决方案

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:

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.)

Form 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()

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

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

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