2011-09-14 84 views
1

如果我運行下面的代碼,事件動作被執行:的PowerShell:招聘事件動作使用表單不執行

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

字符串「的事件動作」顯示。

如果我使用的一種形式,並通過點擊一個按鈕來啓動上面的代碼,

事件動作不執行:

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

只有當我再次點擊該按鈕,第一個事件的行動執行。

隨着第三次單擊第二個事件操作執行等。

如果我快速連續多次點擊,結果是不可預知的。

此外,當我在右上角的關閉窗體與按鈕,

最後一個「開放」的事件動作被執行。

注意:對於測試PowerShell ISE中是優選的,因爲PS控制檯顯示

串只在某些情況下。

有人可以給我一個線索這裏發生了什麼?

在此先感謝!


nimizen。

感謝您的解釋,但我並不真正瞭解,爲什麼StateChanged事件未被觸發或對主腳本可見,直到對錶單執行了一些操作。我希望再次嘗試向我解釋。

我想完成的是一種使用PowerShell和Forms的多線程處理。

我的計劃是這樣的:

'

腳本顯示了形式提供給用戶。

用戶做一些輸入並點擊一個按鈕。 根據用戶的輸入,一組作業以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 = 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() 

回答

0

Powershell的作業的狀態屬性是隻讀的;這意味着在實際開始作業之前,您無法將作業狀態配置爲任何內容。在監視statechanged事件時,它不會觸發,直到click事件再次出現,並且狀態從'running'更改爲'completed',腳本塊執行時'state'被看到。這也是在關閉表單時執行腳本塊的原因。

以下腳本不需要監視事件,而是監視狀態。我假設你想在狀態爲'running'時觸發'statechanged'代碼。

[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({ 
$this.Enabled = $false 
    Write-Host $Job.State " - (Before job started)" 
    $Job = Start-Job {'abc'} 
    Write-Host $Job.State " - (After job started)" 
     If ($Job.State -eq 'Running') { 

       Start-Sleep -Seconds 1 
       Write-Host '*Doing Stuff*' 
       } 

    Write-Host $Job.State " - (After IF scriptblock finished)" 
[System.Windows.Forms.Application]::DoEvents() 
$this.Enabled = $true 

}) 

$Form1.ShowDialog() 

此外,注意行:

$this.Enabled = $false 
[System.Windows.Forms.Application]::DoEvents() 
$this.Enabled = $true 

幾行確保按鈕不排隊click事件。你可以明顯地刪除'寫主機'這一行,我已經將它們留在了這些內部,這樣你就可以看到腳本執行時狀態如何變化。

希望這會有所幫助。

0

有很多關於這個 'enduring mystique' 相結合形式的(StackOverflow上)的問題和答案(或WPF)和.NET事件事件在PowerShell中(如EngineEventsObjectEventsWmiEvents):

他們都來了兩架一點:即使有多個線程設置中,有兩個不同的「聽衆」在一個線程。當您的腳本準備好接收表單事件(使用ShowDialogDoEvents)時,它不能同時收聽.NET事件。反之亦然:如果腳本在處理命令時(例如Start-Sleep或使用命令如Wait-EventWait-Job專門偵聽.NET事件)打開.NET事件,則表單將無法偵聽表單事件。這意味着.NET事件或表單事件正在排隊,因爲您的表單與您試圖創建的.NET偵聽器在同一個線程中。 與nimizen示例一樣,在第一個龜頭中看起來是正確的,在檢查背景工作者的狀態時,您的表單將不會響應所有其他表單事件(按鈕單擊),您必須單擊該按鈕並再次查看是否仍爲‘*Doing Stuff’。要解決這個問題,您可以考慮在循環中結合DoEvents方法,同時不斷檢查背景工作者的狀態,但這看起來並不是一個好方法,請參閱:Use of Application.DoEvents() 所以唯一的出路是(我看到的)是有一個線程在另一個線程中觸發,我認爲只能使用[runspacefactory]::CreateRunspace()來完成,因爲它可以同步對待之間的窗體控件,並且直接觸發窗體事件(例如TextChanged)。

(如果有另一種方式,我渴望學習如何看一個工作例子。)

例如:

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例子,請參閱: Write PowerShell Output (as it happens) to WPF UI Control